diff options
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) |
