aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Tismer <tismer@stackless.com>2020-08-01 14:29:45 +0200
committerChristian Tismer <tismer@stackless.com>2020-09-16 14:56:30 +0200
commitdedbc42b569d0dc25de10712168b99d0844c8e50 (patch)
tree1709327427dfa644d754d85b6a25ba110e1ac378
parent850b6faeaa580176863b3933e13c08b467720937 (diff)
feature_select: Implement True Properties
This feature is now almost fully implemented. TODO: Static properties like `QtWidgets.QApplication.platformName` are skipped for now. They need support by the meta class. Maybe this is a reason to use QtCore.Property instead of vanilla Python property and improve it. With the new infrastructure, we can also consider to add properties which have no equivalent in the Qt implementation. A prominent example is "central_widget". Change-Id: Ia0e32e41de8ab72e3bba74878e61bcbac6da50ea Task-number: PYSIDE-1019 Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io>
-rw-r--r--sources/pyside2/doc/tutorials/expenses/main.py2
-rw-r--r--sources/pyside2/doc/tutorials/expenses/main_snake_prop.py (renamed from sources/pyside2/doc/tutorials/expenses/main_snake_case.py)46
-rw-r--r--sources/pyside2/libpyside/feature_select.cpp164
-rw-r--r--sources/pyside2/libpyside/pysidestaticstrings.cpp4
-rw-r--r--sources/pyside2/libpyside/pysidestaticstrings.h4
-rw-r--r--sources/pyside2/tests/QtCore/CMakeLists.txt2
-rw-r--r--sources/pyside2/tests/QtCore/multiple_feature_test.py29
-rw-r--r--sources/pyside2/tests/QtCore/snake_prop_feature_test.py (renamed from sources/pyside2/tests/QtCore/snake_case_feature_test.py)44
-rw-r--r--sources/shiboken2/generator/shiboken2/cppgenerator.cpp66
-rw-r--r--sources/shiboken2/libshiboken/basewrapper.cpp10
-rw-r--r--sources/shiboken2/libshiboken/basewrapper.h4
-rw-r--r--sources/shiboken2/libshiboken/basewrapper_p.h1
-rw-r--r--sources/shiboken2/libshiboken/bindingmanager.cpp45
-rw-r--r--sources/shiboken2/libshiboken/bindingmanager.h2
-rw-r--r--sources/shiboken2/libshiboken/sbkstaticstrings.cpp4
-rw-r--r--sources/shiboken2/libshiboken/sbkstaticstrings.h4
-rw-r--r--sources/shiboken2/libshiboken/sbkstring.cpp8
-rw-r--r--sources/shiboken2/libshiboken/sbkstring.h1
-rw-r--r--sources/shiboken2/shibokenmodule/files.dir/shibokensupport/__feature__.py26
19 files changed, 366 insertions, 100 deletions
diff --git a/sources/pyside2/doc/tutorials/expenses/main.py b/sources/pyside2/doc/tutorials/expenses/main.py
index 6cc911671..c27576029 100644
--- a/sources/pyside2/doc/tutorials/expenses/main.py
+++ b/sources/pyside2/doc/tutorials/expenses/main.py
@@ -1,6 +1,6 @@
#############################################################################
##
-## Copyright (C) 2019 The Qt Company Ltd.
+## Copyright (C) 2020 The Qt Company Ltd.
## Contact: http://www.qt.io/licensing/
##
## This file is part of the Qt for Python examples of the Qt Toolkit.
diff --git a/sources/pyside2/doc/tutorials/expenses/main_snake_case.py b/sources/pyside2/doc/tutorials/expenses/main_snake_prop.py
index 154396b41..4421980c5 100644
--- a/sources/pyside2/doc/tutorials/expenses/main_snake_case.py
+++ b/sources/pyside2/doc/tutorials/expenses/main_snake_prop.py
@@ -39,14 +39,14 @@
#############################################################################
import sys
-from PySide2.QtCore import Qt, Slot
+from PySide2.QtCore import Qt, Slot, QSize
from PySide2.QtGui import QPainter
from PySide2.QtWidgets import (QAction, QApplication, QHeaderView, QHBoxLayout, QLabel, QLineEdit,
QMainWindow, QPushButton, QTableWidget, QTableWidgetItem,
QVBoxLayout, QWidget)
from PySide2.QtCharts import QtCharts
-from __feature__ import snake_case
+from __feature__ import snake_case, true_property
class Widget(QWidget):
@@ -61,13 +61,13 @@ class Widget(QWidget):
# Left
self.table = QTableWidget()
- self.table.set_column_count(2)
- self.table.set_horizontal_header_labels(["Description", "Price"])
- self.table.horizontal_header().set_section_resize_mode(QHeaderView.Stretch)
+ self.table.column_count = 2
+ self.table.horizontal_header_labels = ["Description", "Price"]
+ self.table.horizontal_header().section_resize_mode = QHeaderView.Stretch
# Chart
self.chart_view = QtCharts.QChartView()
- self.chart_view.set_render_hint(QPainter.Antialiasing)
+ self.chart_view.render_hint = QPainter.Antialiasing
# Right
self.description = QLineEdit()
@@ -78,10 +78,10 @@ class Widget(QWidget):
self.plot = QPushButton("Plot")
# Disabling 'Add' button
- self.add.setEnabled(False)
+ self.add.enabled = False
self.right = QVBoxLayout()
- self.right.set_margin(10)
+ self.right.margin = 10
self.right.add_widget(QLabel("Description"))
self.right.add_widget(self.description)
self.right.add_widget(QLabel("Price"))
@@ -115,41 +115,41 @@ class Widget(QWidget):
@Slot()
def add_element(self):
- des = self.description.text()
- price = self.price.text()
+ des = self.description.text
+ price = self.price.text
self.table.insert_row(self.items)
description_item = QTableWidgetItem(des)
price_item = QTableWidgetItem("{:.2f}".format(float(price)))
- price_item.set_text_alignment(Qt.AlignRight)
+ price_item.text_alignment = Qt.AlignRight
self.table.set_item(self.items, 0, description_item)
self.table.set_item(self.items, 1, price_item)
- self.description.set_text("")
- self.price.set_text("")
+ self.description.text = ""
+ self.price.text = ""
self.items += 1
@Slot()
def check_disable(self, s):
- if not self.description.text() or not self.price.text():
- self.add.set_enabled(False)
+ if not self.description.text or not self.price.text:
+ self.add.enabled = False
else:
- self.add.set_enabled(True)
+ self.add.enabled = True
@Slot()
def plot_data(self):
# Get table information
series = QtCharts.QPieSeries()
- for i in range(self.table.row_count()):
+ for i in range(self.table.row_count):
text = self.table.item(i, 0).text()
number = float(self.table.item(i, 1).text())
series.append(text, number)
chart = QtCharts.QChart()
chart.add_series(series)
- chart.legend().set_alignment(Qt.AlignLeft)
+ chart.legend().alignment = Qt.AlignLeft
self.chart_view.set_chart(chart)
@Slot()
@@ -161,7 +161,7 @@ class Widget(QWidget):
for desc, price in data.items():
description_item = QTableWidgetItem(desc)
price_item = QTableWidgetItem("{:.2f}".format(price))
- price_item.set_text_alignment(Qt.AlignRight)
+ price_item.text_alignment = Qt.AlignRight
self.table.insert_row(self.items)
self.table.set_item(self.items, 0, description_item)
self.table.set_item(self.items, 1, price_item)
@@ -169,14 +169,14 @@ class Widget(QWidget):
@Slot()
def clear_table(self):
- self.table.set_row_count(0)
+ self.table.row_count = 0
self.items = 0
class MainWindow(QMainWindow):
def __init__(self, widget):
QMainWindow.__init__(self)
- self.setWindowTitle("Tutorial")
+ self.window_title = "Tutorial"
# Menu
self.menu = self.menu_bar()
@@ -184,7 +184,7 @@ class MainWindow(QMainWindow):
# Exit QAction
exit_action = QAction("Exit", self)
- exit_action.set_shortcut("Ctrl+Q")
+ exit_action.shortcut = "Ctrl+Q"
exit_action.triggered.connect(self.exit_app)
self.file_menu.add_action(exit_action)
@@ -202,7 +202,7 @@ if __name__ == "__main__":
widget = Widget()
# QMainWindow using QWidget as central widget
window = MainWindow(widget)
- window.resize(800, 600)
+ window.size = QSize(800, 600)
window.show()
# Execute application
diff --git a/sources/pyside2/libpyside/feature_select.cpp b/sources/pyside2/libpyside/feature_select.cpp
index d3beeef7a..a1ba76251 100644
--- a/sources/pyside2/libpyside/feature_select.cpp
+++ b/sources/pyside2/libpyside/feature_select.cpp
@@ -39,6 +39,7 @@
#include "feature_select.h"
#include "pyside.h"
+#include "pysidestaticstrings.h"
#include <shiboken.h>
#include <sbkstaticstrings.h>
@@ -125,7 +126,7 @@ namespace PySide { namespace Feature {
using namespace Shiboken;
-typedef bool(*FeatureProc)(PyTypeObject *type, PyObject *prev_dict);
+typedef bool(*FeatureProc)(PyTypeObject *type, PyObject *prev_dict, int id);
static FeatureProc *featurePointer = nullptr;
@@ -226,7 +227,11 @@ static inline void setCurrentSelectId(PyTypeObject *type, int id)
static inline PyObject *getCurrentSelectId(PyTypeObject *type)
{
- return fast_id_array[SbkObjectType_GetReserved(type)];
+ int id = SbkObjectType_GetReserved(type);
+ // This can be too early.
+ if (id < 0)
+ id = 0;
+ return fast_id_array[id];
}
static bool replaceClassDict(PyTypeObject *type)
@@ -331,7 +336,7 @@ static bool createNewFeatureSet(PyTypeObject *type, PyObject *select_id)
// clear the tp_dict that will get new content
PyDict_Clear(type->tp_dict);
// let the proc re-fill the tp_dict
- if (!(*proc)(type, prev_dict))
+ if (!(*proc)(type, prev_dict, id))
return false;
// if there is still a step, prepare `prev_dict`
if (idx >> 1) {
@@ -413,18 +418,18 @@ void Select(PyObject *obj)
type->tp_dict = SelectFeatureSet(type);
}
-static bool feature_01_addLowerNames(PyTypeObject *type, PyObject *prev_dict);
-static bool feature_02_addDummyNames(PyTypeObject *type, PyObject *prev_dict);
-static bool feature_04_addDummyNames(PyTypeObject *type, PyObject *prev_dict);
-static bool feature_08_addDummyNames(PyTypeObject *type, PyObject *prev_dict);
-static bool feature_10_addDummyNames(PyTypeObject *type, PyObject *prev_dict);
-static bool feature_20_addDummyNames(PyTypeObject *type, PyObject *prev_dict);
-static bool feature_40_addDummyNames(PyTypeObject *type, PyObject *prev_dict);
-static bool feature_80_addDummyNames(PyTypeObject *type, PyObject *prev_dict);
+static bool feature_01_addLowerNames(PyTypeObject *type, PyObject *prev_dict, int id);
+static bool feature_02_true_property(PyTypeObject *type, PyObject *prev_dict, int id);
+static bool feature_04_addDummyNames(PyTypeObject *type, PyObject *prev_dict, int id);
+static bool feature_08_addDummyNames(PyTypeObject *type, PyObject *prev_dict, int id);
+static bool feature_10_addDummyNames(PyTypeObject *type, PyObject *prev_dict, int id);
+static bool feature_20_addDummyNames(PyTypeObject *type, PyObject *prev_dict, int id);
+static bool feature_40_addDummyNames(PyTypeObject *type, PyObject *prev_dict, int id);
+static bool feature_80_addDummyNames(PyTypeObject *type, PyObject *prev_dict, int id);
static FeatureProc featureProcArray[] = {
feature_01_addLowerNames,
- feature_02_addDummyNames,
+ feature_02_true_property,
feature_04_addDummyNames,
feature_08_addDummyNames,
feature_10_addDummyNames,
@@ -471,8 +476,8 @@ void init()
//
static PyObject *methodWithNewName(PyTypeObject *type,
- PyMethodDef *meth,
- const char *new_name)
+ PyMethodDef *meth,
+ const char *new_name)
{
/*
* Create a method with a lower case name.
@@ -499,7 +504,7 @@ static PyObject *methodWithNewName(PyTypeObject *type,
return descr;
}
-static bool feature_01_addLowerNames(PyTypeObject *type, PyObject *prev_dict)
+static bool feature_01_addLowerNames(PyTypeObject *type, PyObject *prev_dict, int id)
{
/*
* Add objects with lower names to `type->tp_dict` from 'prev_dict`.
@@ -520,6 +525,9 @@ static bool feature_01_addLowerNames(PyTypeObject *type, PyObject *prev_dict)
// Then we walk over the tp_methods to get all methods and insert
// them with changed names.
PyMethodDef *meth = type->tp_methods;
+ if (!meth)
+ return true;
+
for (; meth != nullptr && meth->ml_name != nullptr; ++meth) {
const char *name = String::toCString(String::getSnakeCaseName(meth->ml_name, true));
AutoDecRef new_method(methodWithNewName(type, meth, name));
@@ -535,22 +543,140 @@ static bool feature_01_addLowerNames(PyTypeObject *type, PyObject *prev_dict)
//
// PYSIDE-1019: Support switchable extensions
//
-// Feature 0x02..0x80: A fake switchable option for testing
+// Feature 0x02: Use true properties instead of getters and setters
+//
+
+static PyObject *createProperty(PyObject *getter, PyObject *setter, PyObject *doc)
+{
+ assert(getter != nullptr);
+ if (setter == nullptr)
+ setter = Py_None;
+ PyObject *deleter = Py_None;
+ PyObject *prop = PyObject_CallObject(reinterpret_cast<PyObject *>(&PyProperty_Type), nullptr);
+ AutoDecRef args(Py_BuildValue("OOOO", getter, setter, deleter, doc));
+ PyProperty_Type.tp_init(prop, args, nullptr);
+ return prop;
+}
+
+static PyObject *calcPropDocString(PyTypeObject *type, PyObject *getterName, PyObject *setterName)
+{
+ // To calculate the docstring, we need the __doc__ attribute of the original
+ // getter and setter. We temporatily switch back to no features. This
+ // might change when we have full signature support for features.
+ auto hold = type->tp_dict;
+ moveToFeatureSet(type, fast_id_array[0]);
+ auto dict = type->tp_dict;
+ auto getter = PyDict_GetItem(dict, getterName);
+ auto setter = setterName ? PyDict_GetItem(dict, setterName) : nullptr;
+ PyObject *buf = PyObject_GetAttr(getter, PyMagicName::doc());
+ type->tp_dict = hold;
+
+ if (setter == nullptr)
+ return buf;
+ AutoDecRef nl(Py_BuildValue("s", "\n"));
+ AutoDecRef wdoc(PyObject_GetAttr(setter, PyMagicName::doc()));
+ String::concat(&buf, nl);
+ String::concat(&buf, wdoc);
+ return buf;
+}
+
+static QStringList parseFields(const char *propstr)
+{
+ /*
+ * Break the string into subfields at ':' and add defaults.
+ */
+ QString s = QString(QLatin1String(propstr));
+ auto list = s.split(QLatin1Char(':'));
+ assert(list.size() == 2 || list.size() == 3);
+ auto name = list[0];
+ auto read = list[1];
+ if (read.size() == 0)
+ list[1] = name;
+ if (list.size() == 2)
+ return list;
+ auto write = list[2];
+ if (write.size() == 0) {
+ list[2] = QLatin1String("set") + name;
+ list[2][3] = list[2][3].toUpper();
+ }
+ return list;
+}
+
+static PyObject *make_snake_case(QString s, bool lower)
+{
+ if (s.isNull())
+ return nullptr;
+ return String::getSnakeCaseName(s.toLatin1().data(), lower);
+}
+
+static bool feature_02_true_property(PyTypeObject *type, PyObject *prev_dict, int id)
+{
+ /*
+ * Use the property info to create true Python property objects.
+ */
+
+ // The empty `tp_dict` gets populated by the previous dict.
+ PyObject *prop_dict = type->tp_dict;
+ if (PyDict_Update(prop_dict, prev_dict) < 0)
+ return false;
+
+ // We then replace methods by properties.
+ bool lower = (id & 0x01) != 0;
+ auto props = SbkObjectType_GetPropertyStrings(type);
+ if (props == nullptr || *props == nullptr)
+ return true;
+ for (; *props != nullptr; ++props) {
+ auto propstr = *props;
+ auto fields = parseFields(propstr);
+ bool haveWrite = fields.size() == 3;
+ PyObject *name = make_snake_case(fields[0], lower);
+ PyObject *read = make_snake_case(fields[1], lower);
+ PyObject *write = haveWrite ? make_snake_case(fields[2], lower) : nullptr;
+ PyObject *getter = PyDict_GetItem(prev_dict, read);
+ if (getter == nullptr || Py_TYPE(getter) != PepMethodDescr_TypePtr)
+ continue;
+ PyObject *setter = haveWrite ? PyDict_GetItem(prev_dict, write) : nullptr;
+ if (setter != nullptr && Py_TYPE(setter) != PepMethodDescr_TypePtr)
+ continue;
+
+ PyObject *doc_read = make_snake_case(fields[1], false);
+ PyObject *doc_write(haveWrite ? make_snake_case(fields[2], false) : nullptr);
+ AutoDecRef doc(calcPropDocString(type, doc_read, doc_write));
+ AutoDecRef PyProperty(createProperty(getter, setter, doc));
+ if (PyProperty.isNull())
+ return false;
+ if (PyDict_SetItem(prop_dict, name, PyProperty) < 0)
+ return false;
+ if (fields[0] != fields[1] && PyDict_GetItem(prop_dict, read))
+ if (PyDict_DelItem(prop_dict, read) < 0)
+ return false;
+ // Theoretically, we need to check for multiple signatures to be exact.
+ // But we don't do so intentionally because it would be confusing.
+ if (haveWrite && PyDict_GetItem(prop_dict, write))
+ if (PyDict_DelItem(prop_dict, write) < 0)
+ return false;
+ }
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// PYSIDE-1019: Support switchable extensions
+//
+// Feature 0x04..0x40: A fake switchable option for testing
//
#define SIMILAR_FEATURE(xx) \
-static bool feature_##xx##_addDummyNames(PyTypeObject *type, PyObject *prev_dict) \
+static bool feature_##xx##_addDummyNames(PyTypeObject *type, PyObject *prev_dict, int id) \
{ \
PyObject *dict = type->tp_dict; \
if (PyDict_Update(dict, prev_dict) < 0) \
return false; \
- Py_INCREF(Py_None); \
if (PyDict_SetItemString(dict, "fake_feature_" #xx, Py_None) < 0) \
return false; \
return true; \
}
-SIMILAR_FEATURE(02)
SIMILAR_FEATURE(04)
SIMILAR_FEATURE(08)
SIMILAR_FEATURE(10)
diff --git a/sources/pyside2/libpyside/pysidestaticstrings.cpp b/sources/pyside2/libpyside/pysidestaticstrings.cpp
index 82e233621..760d77632 100644
--- a/sources/pyside2/libpyside/pysidestaticstrings.cpp
+++ b/sources/pyside2/libpyside/pysidestaticstrings.cpp
@@ -55,5 +55,9 @@ STATIC_STRING_IMPL(qtStaticMetaObject, "staticMetaObject")
STATIC_STRING_IMPL(qtConnect, "connect")
STATIC_STRING_IMPL(qtDisconnect, "disconnect")
STATIC_STRING_IMPL(qtEmit, "emit")
+STATIC_STRING_IMPL(dict_ring, "dict_ring")
+STATIC_STRING_IMPL(name, "name")
+STATIC_STRING_IMPL(property, "property")
+STATIC_STRING_IMPL(select_id, "select_id")
} // namespace PyName
} // namespace PySide
diff --git a/sources/pyside2/libpyside/pysidestaticstrings.h b/sources/pyside2/libpyside/pysidestaticstrings.h
index 1d5700c51..1222d8f47 100644
--- a/sources/pyside2/libpyside/pysidestaticstrings.h
+++ b/sources/pyside2/libpyside/pysidestaticstrings.h
@@ -50,6 +50,10 @@ PyObject *qtStaticMetaObject();
PyObject *qtConnect();
PyObject *qtDisconnect();
PyObject *qtEmit();
+PyObject *dict_ring();
+PyObject *name();
+PyObject *property();
+PyObject *select_id();
} // namespace PyName
} // namespace PySide
diff --git a/sources/pyside2/tests/QtCore/CMakeLists.txt b/sources/pyside2/tests/QtCore/CMakeLists.txt
index 0c89f0d03..9d268e079 100644
--- a/sources/pyside2/tests/QtCore/CMakeLists.txt
+++ b/sources/pyside2/tests/QtCore/CMakeLists.txt
@@ -128,7 +128,7 @@ PYSIDE_TEST(quuid_test.py)
PYSIDE_TEST(qversionnumber_test.py)
PYSIDE_TEST(repr_test.py)
PYSIDE_TEST(setprop_on_ctor_test.py)
-PYSIDE_TEST(snake_case_feature_test.py)
+PYSIDE_TEST(snake_prop_feature_test.py)
PYSIDE_TEST(staticMetaObject_test.py)
PYSIDE_TEST(static_method_test.py)
PYSIDE_TEST(thread_signals_test.py)
diff --git a/sources/pyside2/tests/QtCore/multiple_feature_test.py b/sources/pyside2/tests/QtCore/multiple_feature_test.py
index 351090382..329e513fb 100644
--- a/sources/pyside2/tests/QtCore/multiple_feature_test.py
+++ b/sources/pyside2/tests/QtCore/multiple_feature_test.py
@@ -48,8 +48,7 @@ from init_paths import init_test_paths
init_test_paths(False)
from PySide2 import QtCore
-from PySide2.support.__feature__ import (
- _really_all_feature_names, pyside_feature_dict)
+from PySide2.support import __feature__
from textwrap import dedent
"""
@@ -62,6 +61,8 @@ The first feature is `snake_case` instead of `camelCase`.
There is much more to come.
"""
+MethodDescriptorType = type(str.split)
+
class FeaturesTest(unittest.TestCase):
def testAllFeatureCombinations(self):
@@ -69,7 +70,7 @@ class FeaturesTest(unittest.TestCase):
Test for all 256 possible combinations of `__feature__` imports.
"""
- def tst_bit0(flag, self):
+ def tst_bit0(flag, self, bits):
if flag == 0:
QtCore.QCborArray.isEmpty
QtCore.QCborArray.__dict__["isEmpty"]
@@ -85,13 +86,25 @@ class FeaturesTest(unittest.TestCase):
with self.assertRaises(KeyError):
QtCore.QCborArray.__dict__["isEmpty"]
+ def tst_bit1(flag, self, bits):
+ getter_name = "object_name" if bits & 1 else "objectName"
+ setter_name = "set_object_name" if bits & 1 else "setObjectName"
+ thing = getattr(QtCore.QObject, getter_name)
+ if flag:
+ self.assertEqual(type(thing), property)
+ with self.assertRaises(AttributeError):
+ getattr(QtCore.QObject, setter_name)
+ else:
+ self.assertEqual(type(thing), MethodDescriptorType)
+ getattr(QtCore.QObject, setter_name)
+
edict = {}
- for bit in range(1, 8):
+ for bit in range(2, 8):
# We are cheating here, since the functions are in the globals.
eval(compile(dedent("""
- def tst_bit{0}(flag, self):
+ def tst_bit{0}(flag, self, bits):
if flag == 0:
with self.assertRaises(AttributeError):
QtCore.QCborArray.fake_feature_{1:02x}
@@ -103,12 +116,12 @@ class FeaturesTest(unittest.TestCase):
""").format(bit, 1 << bit), "<string>", "exec"), globals(), edict)
globals().update(edict)
- feature_list = _really_all_feature_names
+ feature_list = __feature__._really_all_feature_names
func_list = [tst_bit0, tst_bit1, tst_bit2, tst_bit3,
tst_bit4, tst_bit5, tst_bit6, tst_bit7]
for idx in range(0x100):
- pyside_feature_dict.clear()
+ __feature__.set_selection(0)
config = "feature_{:02x}".format(idx)
print()
print("--- Feature Test Config `{}` ---".format(config))
@@ -121,7 +134,7 @@ class FeaturesTest(unittest.TestCase):
eval(compile(text, "<string>", "exec"), globals(), edict)
for bit in range(8):
value = idx & 1 << bit
- func_list[bit](value, self=self)
+ func_list[bit](value, self=self, bits=idx)
if __name__ == '__main__':
diff --git a/sources/pyside2/tests/QtCore/snake_case_feature_test.py b/sources/pyside2/tests/QtCore/snake_prop_feature_test.py
index b7f23396e..779b8a408 100644
--- a/sources/pyside2/tests/QtCore/snake_case_feature_test.py
+++ b/sources/pyside2/tests/QtCore/snake_prop_feature_test.py
@@ -46,41 +46,61 @@ from init_paths import init_test_paths
init_test_paths(False)
from PySide2 import QtWidgets
+from PySide2.support import __feature__
"""
-snake_case_feature_test.py
+snake_prop_feature_test.py
--------------------------
-Test the snake_case feature.
+Test the snake_case and true_property feature.
This works now. More tests needed!
"""
-class RenamingTest(unittest.TestCase):
+class Window(QtWidgets.QWidget):
+ def __init__(self):
+ super(Window, self).__init__()
+
+
+class FeatureTest(unittest.TestCase):
def setUp(self):
qApp or QtWidgets.QApplication()
+ __feature__.set_selection(0)
def tearDown(self):
qApp.shutdown()
def testRenamedFunctions(self):
-
- class Window(QtWidgets.QWidget):
- def __init__(self):
- super(Window, self).__init__()
-
window = Window()
window.setWindowTitle('camelCase')
# and now the same with snake_case enabled
from __feature__ import snake_case
- class Window(QtWidgets.QWidget):
- def __init__(self):
- super(Window, self).__init__()
+ # Works with the same window! window = Window()
+ window.set_window_title('snake_case')
+ def testPropertyAppearVanish(self):
window = Window()
- window.set_window_title('snake_case')
+
+ self.assertTrue(callable(window.isModal))
+ with self.assertRaises(AttributeError):
+ window.modal
+
+ from __feature__ import snake_case, true_property
+
+ self.assertTrue(isinstance(QtWidgets.QWidget.modal, property))
+ self.assertTrue(isinstance(window.modal, bool))
+ with self.assertRaises(AttributeError):
+ window.isModal
+
+ # switching back
+ __feature__.set_selection(0)
+
+ self.assertTrue(callable(window.isModal))
+ with self.assertRaises(AttributeError):
+ window.modal
+
if __name__ == '__main__':
unittest.main()
diff --git a/sources/shiboken2/generator/shiboken2/cppgenerator.cpp b/sources/shiboken2/generator/shiboken2/cppgenerator.cpp
index 8dba64818..d38463e2c 100644
--- a/sources/shiboken2/generator/shiboken2/cppgenerator.cpp
+++ b/sources/shiboken2/generator/shiboken2/cppgenerator.cpp
@@ -288,6 +288,34 @@ static inline bool canGenerateFieldSetter(const AbstractMetaField *field)
return !type->isConstant() || isPointerToConst(type);
}
+static bool isStdSetterName(QString setterName, QString propertyName)
+{
+ return setterName.size() == propertyName.size() + 3
+ && setterName.startsWith(QLatin1String("set"))
+ && setterName.endsWith(propertyName.rightRef(propertyName.size() - 1))
+ && setterName.at(3) == propertyName.at(0).toUpper();
+}
+
+static QString buildPropertyString(QPropertySpec *spec)
+{
+ QString text;
+ text += QLatin1Char('"');
+ text += spec->name();
+ text += QLatin1Char(':');
+
+ if (spec->read() != spec->name())
+ text += spec->read();
+
+ if (!spec->write().isEmpty()) {
+ text += QLatin1Char(':');
+ if (!isStdSetterName(spec->write(), spec->name()))
+ text += spec->write();
+ }
+
+ text += QLatin1Char('"');
+ return text;
+}
+
/*!
Function used to write the class generated binding code on the buffer
\param s the output buffer
@@ -556,6 +584,22 @@ void CppGenerator::generateClass(QTextStream &s, const GeneratorContext &classCo
// Write single method definitions
s << singleMethodDefinitions;
+ if (usePySideExtensions()) {
+ // PYSIDE-1019: Write a compressed list of all properties `name:getter[:setter]`.
+ // Default values are suppressed.
+ QStringList sorter;
+ for (const auto spec : metaClass->propertySpecs())
+ sorter.append(buildPropertyString(spec));
+ sorter.sort();
+
+ s << '\n';
+ s << "static const char *" << className << "_properties[] = {\n";
+ for (const auto &entry : qAsConst(sorter))
+ s << INDENT << entry << ",\n";
+ s << INDENT << NULL_PTR << " // Sentinel\n";
+ s << "};\n\n";
+ }
+
// Write methods definition
s << "static PyMethodDef " << className << "_methods[] = {\n";
s << methodsDefinitions << Qt::endl;
@@ -951,8 +995,22 @@ void CppGenerator::writeVirtualMethodNative(QTextStream &s,
s << INDENT << returnStatement << '\n';
}
+ //PYSIDE-1019: Add info about properties.
+ int propFlag = 0;
+ if (func->isPropertyReader())
+ propFlag |= 1;
+ if (func->isPropertyWriter())
+ propFlag |= 2;
+ if (propFlag && func->isStatic())
+ propFlag |= 4;
+ QString propStr;
+ if (propFlag)
+ propStr = QString::number(propFlag) + QLatin1Char(':');
+
s << INDENT << "static PyObject *nameCache[2] = {};\n";
- s << INDENT << "static const char *funcName = \"" << funcName << "\";\n";
+ if (propFlag)
+ s << INDENT << "// This method belongs to a property.\n";
+ s << INDENT << "static const char *funcName = \"" << propStr << funcName << "\";\n";
s << INDENT << "Shiboken::AutoDecRef " << PYTHON_OVERRIDE_VAR
<< "(Shiboken::BindingManager::instance().getOverride(this, nameCache, funcName));\n";
s << INDENT << "if (" << PYTHON_OVERRIDE_VAR << ".isNull()) {\n";
@@ -5159,6 +5217,12 @@ void CppGenerator::writeClassRegister(QTextStream &s,
s << INDENT << ");\n";
s << INDENT << Qt::endl;
+ if (usePySideExtensions()) {
+ QString className = metaClass->qualifiedCppName();
+ s << INDENT << "SbkObjectType_SetPropertyStrings(reinterpret_cast<PyTypeObject *>(" << typePtr << "), "
+ << chopType(pyTypeName) << "_properties);\n";
+ }
+
if (!classContext.forSmartPointer())
s << INDENT << cpythonTypeNameExt(classTypeEntry) << Qt::endl;
else
diff --git a/sources/shiboken2/libshiboken/basewrapper.cpp b/sources/shiboken2/libshiboken/basewrapper.cpp
index decfd01db..6c4dea642 100644
--- a/sources/shiboken2/libshiboken/basewrapper.cpp
+++ b/sources/shiboken2/libshiboken/basewrapper.cpp
@@ -615,6 +615,16 @@ void SbkObjectType_SetReserved(PyTypeObject *type, int value)
PepType_SOTP(reinterpret_cast<SbkObjectType *>(type))->pyside_reserved_bits = value;
}
+const char **SbkObjectType_GetPropertyStrings(PyTypeObject *type)
+{
+ return PepType_SOTP(type)->propertyStrings;
+}
+
+void SbkObjectType_SetPropertyStrings(PyTypeObject *type, const char **strings)
+{
+ PepType_SOTP(reinterpret_cast<SbkObjectType *>(type))->propertyStrings = strings;
+}
+
//
//////////////////////////////////////////////////////////////////////////////
diff --git a/sources/shiboken2/libshiboken/basewrapper.h b/sources/shiboken2/libshiboken/basewrapper.h
index 4fec74464..1190f3187 100644
--- a/sources/shiboken2/libshiboken/basewrapper.h
+++ b/sources/shiboken2/libshiboken/basewrapper.h
@@ -101,6 +101,10 @@ LIBSHIBOKEN_API void initSelectableFeature(SelectableFeatureHook func);
LIBSHIBOKEN_API int SbkObjectType_GetReserved(PyTypeObject *type);
LIBSHIBOKEN_API void SbkObjectType_SetReserved(PyTypeObject *type, int value);
+// PYSIDE-1019: Get access to PySide property strings.
+LIBSHIBOKEN_API const char **SbkObjectType_GetPropertyStrings(PyTypeObject *type);
+LIBSHIBOKEN_API void SbkObjectType_SetPropertyStrings(PyTypeObject *type, const char **strings);
+
extern LIBSHIBOKEN_API PyTypeObject *SbkObjectType_TypeF(void);
extern LIBSHIBOKEN_API SbkObjectType *SbkObject_TypeF(void);
diff --git a/sources/shiboken2/libshiboken/basewrapper_p.h b/sources/shiboken2/libshiboken/basewrapper_p.h
index 482a3ea2a..64f7941b7 100644
--- a/sources/shiboken2/libshiboken/basewrapper_p.h
+++ b/sources/shiboken2/libshiboken/basewrapper_p.h
@@ -146,6 +146,7 @@ struct SbkObjectTypePrivate
void *user_data;
DeleteUserDataFunc d_func;
void (*subtype_init)(SbkObjectType *, PyObject *, PyObject *);
+ const char **propertyStrings;
};
diff --git a/sources/shiboken2/libshiboken/bindingmanager.cpp b/sources/shiboken2/libshiboken/bindingmanager.cpp
index 7b06a4a00..78c03556c 100644
--- a/sources/shiboken2/libshiboken/bindingmanager.cpp
+++ b/sources/shiboken2/libshiboken/bindingmanager.cpp
@@ -274,13 +274,17 @@ SbkObject *BindingManager::retrieveWrapper(const void *cptr)
return iter->second;
}
-static inline bool mangleNameFlag(PyTypeObject *type)
+static inline int currentSelectId(PyTypeObject *type)
{
- // PYSIDE-1019: See if a dict is set with a snake_case bit.
- return (SbkObjectType_GetReserved(type) & 1) != 0;
+ int sel = SbkObjectType_GetReserved(type);
+ // This could theoretically be -1 if used too early.
+ assert(sel >= 0);
+ return sel;
}
-PyObject *BindingManager::getOverride(const void *cptr, PyObject *methodNameCache[2], const char *methodName)
+PyObject *BindingManager::getOverride(const void *cptr,
+ PyObject *nameCache[],
+ const char *methodName)
{
SbkObject *wrapper = retrieveWrapper(cptr);
// The refcount can be 0 if the object is dieing and someone called
@@ -288,37 +292,40 @@ PyObject *BindingManager::getOverride(const void *cptr, PyObject *methodNameCach
if (!wrapper || reinterpret_cast<const PyObject *>(wrapper)->ob_refcnt == 0)
return nullptr;
- bool flag = mangleNameFlag(Py_TYPE(wrapper));
- PyObject *pyMethodName = methodNameCache[flag]; // borrowed
+ int flag = currentSelectId(Py_TYPE(wrapper));
+ int propFlag = isdigit(methodName[0]) ? methodName[0] - '0' : 0;
+ if ((flag & 0x02) != 0 && (propFlag & 3) != 0) {
+ // PYSIDE-1019: Handle overriding with properties.
+ // They cannot be overridden (make that sure by the metaclass).
+ return nullptr;
+ }
+ PyObject *pyMethodName = nameCache[(flag & 1) != 0]; // borrowed
if (pyMethodName == nullptr) {
+ if (propFlag)
+ methodName += 2; // skip the propFlag and ':'
pyMethodName = Shiboken::String::getSnakeCaseName(methodName, flag);
- methodNameCache[flag] = pyMethodName;
+ nameCache[(flag & 1) != 0] = pyMethodName;
}
if (wrapper->ob_dict) {
PyObject *method = PyDict_GetItem(wrapper->ob_dict, pyMethodName);
if (method) {
- Py_INCREF(reinterpret_cast<PyObject *>(method));
+ Py_INCREF(method);
return method;
}
}
PyObject *method = PyObject_GetAttr(reinterpret_cast<PyObject *>(wrapper), pyMethodName);
- // PYSIDE-198: Support for Nuitka compiled methods.
- bool isMethod = method && PyMethod_Check(method);
- bool isCompiled = !( isMethod
- || Py_TYPE(method) == &PyCFunction_Type
- || Py_TYPE(method)->tp_call == nullptr);
- Shiboken::AutoDecRef meth_self(PyObject_GetAttr(method, Shiboken::PyMagicName::self()));
- bool wrapsParent = meth_self.object() == reinterpret_cast<PyObject *>(wrapper);
- if ((isMethod && wrapsParent) || isCompiled) {
+ if (method && PyMethod_Check(method)
+ && PyMethod_GET_SELF(method) == reinterpret_cast<PyObject *>(wrapper)) {
PyObject *defaultMethod;
PyObject *mro = Py_TYPE(wrapper)->tp_mro;
+ int size = PyTuple_GET_SIZE(mro);
// The first class in the mro (index 0) is the class being checked and it should not be tested.
// The last class in the mro (size - 1) is the base Python object class which should not be tested also.
- for (int idx = 1; idx < PyTuple_GET_SIZE(mro) - 1; ++idx) {
+ for (int idx = 1; idx < size - 1; ++idx) {
auto *parent = reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(mro, idx));
if (parent->tp_dict) {
defaultMethod = PyDict_GetItem(parent->tp_dict, pyMethodName);
@@ -326,9 +333,9 @@ PyObject *BindingManager::getOverride(const void *cptr, PyObject *methodNameCach
return method;
}
}
+ } else {
+ Py_XDECREF(method);
}
-
- Py_XDECREF(method);
return nullptr;
}
diff --git a/sources/shiboken2/libshiboken/bindingmanager.h b/sources/shiboken2/libshiboken/bindingmanager.h
index 8882f402e..5b2246685 100644
--- a/sources/shiboken2/libshiboken/bindingmanager.h
+++ b/sources/shiboken2/libshiboken/bindingmanager.h
@@ -73,7 +73,7 @@ public:
void addToDeletionInMainThread(const DestructorEntry &);
SbkObject *retrieveWrapper(const void *cptr);
- PyObject *getOverride(const void *cptr, PyObject *methodNameCache[2], const char *methodName);
+ PyObject *getOverride(const void *cptr, PyObject *nameCache[], const char *methodName);
void addClassInheritance(SbkObjectType *parent, SbkObjectType *child);
/**
diff --git a/sources/shiboken2/libshiboken/sbkstaticstrings.cpp b/sources/shiboken2/libshiboken/sbkstaticstrings.cpp
index 564853edb..672be4009 100644
--- a/sources/shiboken2/libshiboken/sbkstaticstrings.cpp
+++ b/sources/shiboken2/libshiboken/sbkstaticstrings.cpp
@@ -52,11 +52,11 @@ namespace Shiboken
{
namespace PyName {
// exported:
-STATIC_STRING_IMPL(dict_ring, "dict_ring")
STATIC_STRING_IMPL(dumps, "dumps")
+STATIC_STRING_IMPL(fget, "fget")
+STATIC_STRING_IMPL(fset, "fset")
STATIC_STRING_IMPL(loads, "loads")
STATIC_STRING_IMPL(result, "result")
-STATIC_STRING_IMPL(select_id, "select_id")
STATIC_STRING_IMPL(value, "value")
STATIC_STRING_IMPL(values, "values")
diff --git a/sources/shiboken2/libshiboken/sbkstaticstrings.h b/sources/shiboken2/libshiboken/sbkstaticstrings.h
index d8744bd8d..09e22b395 100644
--- a/sources/shiboken2/libshiboken/sbkstaticstrings.h
+++ b/sources/shiboken2/libshiboken/sbkstaticstrings.h
@@ -49,13 +49,13 @@ namespace Shiboken
namespace PyName
{
LIBSHIBOKEN_API PyObject *co_name();
-LIBSHIBOKEN_API PyObject *dict_ring();
LIBSHIBOKEN_API PyObject *dumps();
+LIBSHIBOKEN_API PyObject *fget();
+LIBSHIBOKEN_API PyObject *fset();
LIBSHIBOKEN_API PyObject *f_code();
LIBSHIBOKEN_API PyObject *f_lineno();
LIBSHIBOKEN_API PyObject *loads();
LIBSHIBOKEN_API PyObject *result();
-LIBSHIBOKEN_API PyObject *select_id();
LIBSHIBOKEN_API PyObject *value();
LIBSHIBOKEN_API PyObject *values();
} // namespace PyName
diff --git a/sources/shiboken2/libshiboken/sbkstring.cpp b/sources/shiboken2/libshiboken/sbkstring.cpp
index ed8b61fc8..ba1cd1e47 100644
--- a/sources/shiboken2/libshiboken/sbkstring.cpp
+++ b/sources/shiboken2/libshiboken/sbkstring.cpp
@@ -315,5 +315,13 @@ PyObject *getSnakeCaseName(const char *name, bool lower)
return createStaticString(new_name);
}
+PyObject *getSnakeCaseName(PyObject *name, bool lower)
+{
+ // This is all static strings, not refcounted.
+ if (lower)
+ return getSnakeCaseName(toCString(name), lower);
+ return name;
+}
+
} // namespace String
} // namespace Shiboken
diff --git a/sources/shiboken2/libshiboken/sbkstring.h b/sources/shiboken2/libshiboken/sbkstring.h
index 3475d3acd..817b8acc2 100644
--- a/sources/shiboken2/libshiboken/sbkstring.h
+++ b/sources/shiboken2/libshiboken/sbkstring.h
@@ -62,6 +62,7 @@ namespace String
LIBSHIBOKEN_API Py_ssize_t len(PyObject *str);
LIBSHIBOKEN_API PyObject *createStaticString(const char *str);
LIBSHIBOKEN_API PyObject *getSnakeCaseName(const char *name, bool lower);
+ LIBSHIBOKEN_API PyObject *getSnakeCaseName(PyObject *name, bool lower);
} // namespace String
} // namespace Shiboken
diff --git a/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/__feature__.py b/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/__feature__.py
index 3852b3463..64f654d30 100644
--- a/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/__feature__.py
+++ b/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/__feature__.py
@@ -60,7 +60,7 @@ import sys
all_feature_names = [
"snake_case",
- "_feature_02",
+ "true_property",
"_feature_04",
"_feature_08",
"_feature_10",
@@ -71,8 +71,8 @@ all_feature_names = [
__all__ = ["all_feature_names", "set_selection", "info"] + all_feature_names
-snake_case = 1
-_feature_02 = 0x02
+snake_case = 0x01
+true_property = 0x02
_feature_04 = 0x04
_feature_08 = 0x08
_feature_10 = 0x10
@@ -108,13 +108,7 @@ def _import(name, *args, **kwargs):
existing = pyside_feature_dict.get(importing_module, 0)
if name == "__feature__" and args[2]:
- global _is_initialized
- if not _is_initialized:
- # use _one_ recursive import...
- import PySide2.QtCore
- # Initialize all prior imported modules
- for name in sys.modules:
- pyside_feature_dict.setdefault(name, -1)
+ __init__()
# This is an `import from` statement that corresponds to `IMPORT_NAME`.
# The following `IMPORT_FROM` will handle errors. (Confusing, ofc.)
@@ -147,6 +141,15 @@ def _import(name, *args, **kwargs):
_is_initialized = False
+def __init__():
+ global _is_initialized
+ if not _is_initialized:
+ # use _one_ recursive import...
+ import PySide2.QtCore
+ # Initialize all prior imported modules
+ for name in sys.modules:
+ pyside_feature_dict.setdefault(name, -1)
+
def set_selection(select_id, mod_name=None):
"""
@@ -154,11 +157,12 @@ def set_selection(select_id, mod_name=None):
Id == -1: ignore this module in switching.
"""
mod_name = mod_name or sys._getframe(1).f_globals['__name__']
+ __init__()
# Reset the features to the given id
flag = 0
if isinstance(select_id, int):
flag = select_id & 255
- pyside_feature_dict[importing_module] = flag
+ pyside_feature_dict[mod_name] = flag
sys.modules["PySide2.QtCore"].__init_feature__()
return _current_selection(flag)