diff options
-rw-r--r-- | sources/pyside2/libpyside/CMakeLists.txt | 2 | ||||
-rw-r--r-- | sources/pyside2/libpyside/class_property.cpp | 158 | ||||
-rw-r--r-- | sources/pyside2/libpyside/class_property.h | 69 | ||||
-rw-r--r-- | sources/pyside2/libpyside/feature_select.cpp | 71 | ||||
-rw-r--r-- | sources/pyside2/libpyside/feature_select.h | 1 | ||||
-rw-r--r-- | sources/pyside2/tests/QtCore/snake_prop_feature_test.py | 19 | ||||
-rw-r--r-- | sources/shiboken2/libshiboken/pep384impl.cpp | 81 | ||||
-rw-r--r-- | sources/shiboken2/libshiboken/pep384impl.h | 6 | ||||
-rw-r--r-- | sources/shiboken2/libshiboken/sbkstaticstrings.h | 1 | ||||
-rw-r--r-- | sources/shiboken2/libshiboken/sbkstaticstrings_p.h | 1 |
10 files changed, 387 insertions, 22 deletions
diff --git a/sources/pyside2/libpyside/CMakeLists.txt b/sources/pyside2/libpyside/CMakeLists.txt index e31c87eef..5e6ee86ba 100644 --- a/sources/pyside2/libpyside/CMakeLists.txt +++ b/sources/pyside2/libpyside/CMakeLists.txt @@ -39,6 +39,7 @@ else() endif() set(libpyside_SRC + class_property.cpp dynamicqmetaobject.cpp feature_select.cpp signalmanager.cpp @@ -125,6 +126,7 @@ endif() # set(libpyside_HEADERS + class_property.h dynamicqmetaobject.h feature_select.h pysideclassinfo.h diff --git a/sources/pyside2/libpyside/class_property.cpp b/sources/pyside2/libpyside/class_property.cpp new file mode 100644 index 000000000..90ce0d2f3 --- /dev/null +++ b/sources/pyside2/libpyside/class_property.cpp @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt for Python. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "pyside.h" +#include "pysidestaticstrings.h" +#include "feature_select.h" +#include "class_property.h" + +#include <shiboken.h> +#include <sbkstaticstrings.h> + +extern "C" { + +/* + * A `classproperty` is the same as a `property` but the `__get__()` and `__set__()` + * methods are modified to always use the object class instead of a concrete instance. + * + * Note: A "static property" as it is often called does not exist per se. + * Static methods do not receive anything when created. Static methods which + * should participate in a property must be turned into class methods, before. + * See function `createProperty` in `feature_select.cpp`. + */ + +// `class_property.__get__()`: Always pass the class instead of the instance. +static PyObject *PyClassProperty_get(PyObject *self, PyObject * /*ob*/, PyObject *cls) +{ + return PyProperty_Type.tp_descr_get(self, cls, cls); +} + +// `class_property.__set__()`: Just like the above `__get__()`. +static int PyClassProperty_set(PyObject *self, PyObject *obj, PyObject *value) +{ + PyObject *cls = PyType_Check(obj) ? obj : reinterpret_cast<PyObject *>(Py_TYPE(obj)); + return PyProperty_Type.tp_descr_set(self, cls, value); +} + +// The property `__doc__` default does not work for class properties +// because PyProperty_Type.tp_init thinks this is a subclass which needs PyObject_SetAttr. +// We call `__init__` while pretending to be a PyProperty_Type instance. +static int PyClassProperty_init(PyObject *self, PyObject *args, PyObject *kwargs) +{ + auto hold = Py_TYPE(self); + Py_TYPE(self) = &PyProperty_Type; + auto ret = PyProperty_Type.tp_init(self, args, kwargs); + Py_TYPE(self) = hold; + return ret; +} + +static PyType_Slot PyClassProperty_slots[] = { + {Py_tp_getset, nullptr}, // will be set below + {Py_tp_base, reinterpret_cast<void *>(&PyProperty_Type)}, + {Py_tp_descr_get, reinterpret_cast<void *>(PyClassProperty_get)}, + {Py_tp_descr_set, reinterpret_cast<void *>(PyClassProperty_set)}, + {Py_tp_init, reinterpret_cast<void *>(PyClassProperty_init)}, + {0, 0} +}; + +static PyType_Spec PyClassProperty_spec = { + "PySide2.PyClassProperty", + sizeof(propertyobject), + 0, + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, + PyClassProperty_slots, +}; + +PyTypeObject *PyClassPropertyTypeF() +{ + static PyTypeObject *type = nullptr; + if (type == nullptr) { + // Provide the same `tp_getset`, which is not inherited. + PyClassProperty_slots[0].pfunc = PyProperty_Type.tp_getset; + type = reinterpret_cast<PyTypeObject *>( + PyType_FromSpec(&PyClassProperty_spec)); + } + return type; +} + +/* + * Types with class properties need to handle `Type.class_prop = x` in a specific way. + * By default, Python replaces the `class_property` itself, but for wrapped C++ types + * we need to call `class_property.__set__()` in order to propagate the new value to + * the underlying C++ data structure. + */ +static int SbkObjectType_meta_setattro(PyObject *obj, PyObject *name, PyObject *value) +{ + // Use `_PepType_Lookup()` instead of `PyObject_GetAttr()` in order to get the raw + // descriptor (`property`) instead of calling `tp_descr_get` (`property.__get__()`). + auto type = reinterpret_cast<PyTypeObject *>(obj); + PyObject *descr = _PepType_Lookup(type, name); + + // The following assignment combinations are possible: + // 1. `Type.class_prop = value` --> descr_set: `Type.class_prop.__set__(value)` + // 2. `Type.class_prop = other_class_prop` --> setattro: replace existing `class_prop` + // 3. `Type.regular_attribute = value` --> setattro: regular attribute assignment + const auto class_prop = reinterpret_cast<PyObject *>(PyClassPropertyTypeF()); + const auto call_descr_set = descr && PyObject_IsInstance(descr, class_prop) + && !PyObject_IsInstance(value, class_prop); + if (call_descr_set) { + // Call `class_property.__set__()` instead of replacing the `class_property`. + return Py_TYPE(descr)->tp_descr_set(descr, obj, value); + } else { + // Replace existing attribute. + return PyType_Type.tp_setattro(obj, name, value); + } +} + +} // extern "C" + +/* + * These functions are added to the SbkObjectType_TypeF() dynamically. + */ +namespace PySide { namespace ClassProperty { + +void init() +{ + PyTypeObject *type = SbkObjectType_TypeF(); + type->tp_setattro = SbkObjectType_meta_setattro; + Py_TYPE(PyClassPropertyTypeF()) = type; +} + +} // namespace ClassProperty +} // namespace PySide diff --git a/sources/pyside2/libpyside/class_property.h b/sources/pyside2/libpyside/class_property.h new file mode 100644 index 000000000..f94fdde31 --- /dev/null +++ b/sources/pyside2/libpyside/class_property.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt for Python. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CLASS_PROPERTY_H +#define CLASS_PROPERTY_H + +#include "pysidemacros.h" +#include <sbkpython.h> + +extern "C" { + +typedef struct { + PyObject_HEAD + PyObject *prop_get; + PyObject *prop_set; + PyObject *prop_del; + PyObject *prop_doc; + int getter_doc; +} propertyobject; + +PYSIDE_API PyTypeObject *PyClassPropertyTypeF(); + +} // extern "C" + +namespace PySide { +namespace ClassProperty { + +PYSIDE_API void init(); + +} // namespace ClassProperty +} // namespace PySide + +#endif // CLASS_PROPERTY_H diff --git a/sources/pyside2/libpyside/feature_select.cpp b/sources/pyside2/libpyside/feature_select.cpp index 6a21d168d..660547d99 100644 --- a/sources/pyside2/libpyside/feature_select.cpp +++ b/sources/pyside2/libpyside/feature_select.cpp @@ -40,12 +40,11 @@ #include "feature_select.h" #include "pyside.h" #include "pysidestaticstrings.h" +#include "class_property.h" #include <shiboken.h> #include <sbkstaticstrings.h> -#include <QtCore/QtGlobal> - ////////////////////////////////////////////////////////////////////////////// // // PYSIDE-1019: Support switchable extensions @@ -418,6 +417,13 @@ void Select(PyObject *obj) type->tp_dict = SelectFeatureSet(type); } +PyObject *Select(PyTypeObject *type) +{ + if (featurePointer != nullptr) + type->tp_dict = SelectFeatureSet(type); + return type->tp_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); @@ -460,6 +466,7 @@ void init() initSelectableFeature(SelectFeatureSet); registerCleanupFunction(finalize); patch_property_impl(); + PySide::ClassProperty::init(); is_initialized = true; } // Reset the cache. This is called at any "from __feature__ import". @@ -549,12 +556,47 @@ static bool feature_01_addLowerNames(PyTypeObject *type, PyObject *prev_dict, in // Feature 0x02: Use true properties instead of getters and setters // -static PyObject *createProperty(PyObject *getter, PyObject *setter) +// This is the Python 2 version for inspection of m_ml, only. +// The actual Python 3 version is larget. + +typedef struct { + PyObject_HEAD + PyMethodDef *m_ml; /* Description of the C function to call */ + PyObject *m_self; /* Passed as 'self' arg to the C func, can be NULL */ + PyObject *m_module; /* The __module__ attribute, can be anything */ +} PyCFunctionObject; + +static PyObject *modifyStaticToClassMethod(PyTypeObject *type, PyObject *sm) { + AutoDecRef func_ob(PyObject_GetAttr(sm, PyMagicName::func())); + if (func_ob.isNull()) + return nullptr; + auto func = reinterpret_cast<PyCFunctionObject *>(func_ob.object()); + auto new_func = new PyMethodDef; + new_func->ml_name = func->m_ml->ml_name; + new_func->ml_meth = func->m_ml->ml_meth; + new_func->ml_flags = (func->m_ml->ml_flags & ~METH_STATIC) | METH_CLASS; + new_func->ml_doc = func->m_ml->ml_doc; + auto cfunc = PyCFunction_NewEx(new_func, nullptr, nullptr); + cfunc = PyDescr_NewClassMethod(type, new_func); + return cfunc; +} + +static PyObject *createProperty(PyTypeObject *type, PyObject *getter, PyObject *setter) +{ + bool chassprop = false; assert(getter != nullptr); if (setter == nullptr) setter = Py_None; - auto obtype = reinterpret_cast<PyObject *>(&PyProperty_Type); + auto ptype = &PyProperty_Type; + if (Py_TYPE(getter) == PepStaticMethod_TypePtr) { + ptype = PyClassPropertyTypeF(); + chassprop = true; + getter = modifyStaticToClassMethod(type, getter); + if (setter != Py_None) + setter = modifyStaticToClassMethod(type, setter); + } + auto obtype = reinterpret_cast<PyObject *>(ptype); PyObject *prop = PyObject_CallFunctionObjArgs(obtype, getter, setter, nullptr); return prop; } @@ -612,13 +654,12 @@ static bool feature_02_true_property(PyTypeObject *type, PyObject *prev_dict, in 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) + if (getter == nullptr || !(Py_TYPE(getter) == PepMethodDescr_TypePtr || + Py_TYPE(getter) == PepStaticMethod_TypePtr)) continue; PyObject *setter = haveWrite ? PyDict_GetItem(prev_dict, write) : nullptr; - if (setter != nullptr && Py_TYPE(setter) != PepMethodDescr_TypePtr) - continue; - AutoDecRef PyProperty(createProperty(getter, setter)); + AutoDecRef PyProperty(createProperty(type, getter, setter)); if (PyProperty.isNull()) return false; if (PyDict_SetItem(prop_dict, name, PyProperty) < 0) @@ -641,19 +682,10 @@ static bool feature_02_true_property(PyTypeObject *type, PyObject *prev_dict, in // suitable for us. // We turn `__doc__` into a lazy attribute saving signature initialization. // -// Currently, there is no static extension planned, because _PyType_Lookup -// and Limited_API are hard to use at the same time. +// There is now also a class property implementation which inherits +// from this one. // -typedef struct { - PyObject_HEAD - PyObject *prop_get; - PyObject *prop_set; - PyObject *prop_del; - PyObject *prop_doc; - int getter_doc; -} propertyobject; - static PyObject *property_doc_get(PyObject *self, void *) { auto po = reinterpret_cast<propertyobject *>(self); @@ -702,7 +734,6 @@ static bool patch_property_impl() return false; if (PyDict_SetItemString(dict, gsp->name, descr) < 0) return false; - // Replace property_descr_get/set by slightly changed versions return true; } diff --git a/sources/pyside2/libpyside/feature_select.h b/sources/pyside2/libpyside/feature_select.h index 32abffac6..845828a4c 100644 --- a/sources/pyside2/libpyside/feature_select.h +++ b/sources/pyside2/libpyside/feature_select.h @@ -48,6 +48,7 @@ namespace Feature { PYSIDE_API void init(); PYSIDE_API void Select(PyObject *obj); +PYSIDE_API PyObject *Select(PyTypeObject *type); } // namespace Feature } // namespace PySide diff --git a/sources/pyside2/tests/QtCore/snake_prop_feature_test.py b/sources/pyside2/tests/QtCore/snake_prop_feature_test.py index 779b8a408..b53a1f0a5 100644 --- a/sources/pyside2/tests/QtCore/snake_prop_feature_test.py +++ b/sources/pyside2/tests/QtCore/snake_prop_feature_test.py @@ -54,7 +54,7 @@ snake_prop_feature_test.py Test the snake_case and true_property feature. -This works now. More tests needed! +This works now, including class properties. """ class Window(QtWidgets.QWidget): @@ -68,6 +68,7 @@ class FeatureTest(unittest.TestCase): __feature__.set_selection(0) def tearDown(self): + __feature__.set_selection(0) qApp.shutdown() def testRenamedFunctions(self): @@ -101,6 +102,22 @@ class FeatureTest(unittest.TestCase): with self.assertRaises(AttributeError): window.modal + def testClassProperty(self): + from __feature__ import snake_case, true_property + # We check the class... + self.assertEqual(type(QtWidgets.QApplication.quit_on_last_window_closed), bool) + x = QtWidgets.QApplication.quit_on_last_window_closed + QtWidgets.QApplication.quit_on_last_window_closed = not x + self.assertEqual(QtWidgets.QApplication.quit_on_last_window_closed, not x) + # ... and now the instance. + self.assertEqual(type(qApp.quit_on_last_window_closed), bool) + x = qApp.quit_on_last_window_closed + qApp.quit_on_last_window_closed = not x + self.assertEqual(qApp.quit_on_last_window_closed, not x) + # make sure values are equal + self.assertEqual(qApp.quit_on_last_window_closed, + QtWidgets.QApplication.quit_on_last_window_closed) + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken2/libshiboken/pep384impl.cpp b/sources/shiboken2/libshiboken/pep384impl.cpp index fa31ce43f..4df9bc21d 100644 --- a/sources/shiboken2/libshiboken/pep384impl.cpp +++ b/sources/shiboken2/libshiboken/pep384impl.cpp @@ -192,6 +192,87 @@ check_PyTypeObject_valid() /***************************************************************************** * + * Additional for object.h / class properties + * + */ +#ifdef Py_LIMITED_API +/* + * This implementation of `_PyType_Lookup` works for lookup in our classes. + * The implementation ignores all caching and versioning and is also + * less optimized. This is reduced from the Python implementation. + */ + +/* Internal API to look for a name through the MRO, bypassing the method cache. + This returns a borrowed reference, and might set an exception. + 'error' is set to: -1: error with exception; 1: error without exception; 0: ok */ +static PyObject * +find_name_in_mro(PyTypeObject *type, PyObject *name, int *error) +{ + Py_ssize_t i, n; + PyObject *mro, *res, *base, *dict; + + /* Look in tp_dict of types in MRO */ + mro = type->tp_mro; + + res = nullptr; + /* Keep a strong reference to mro because type->tp_mro can be replaced + during dict lookup, e.g. when comparing to non-string keys. */ + Py_INCREF(mro); + assert(PyTuple_Check(mro)); + n = PyTuple_GET_SIZE(mro); + for (i = 0; i < n; i++) { + base = PyTuple_GET_ITEM(mro, i); + assert(PyType_Check(base)); + dict = ((PyTypeObject *)base)->tp_dict; + assert(dict && PyDict_Check(dict)); + res = PyDict_GetItem(dict, name); + if (res != nullptr) + break; + if (PyErr_Occurred()) { + *error = -1; + goto done; + } + } + *error = 0; +done: + Py_DECREF(mro); + return res; +} + +/* Internal API to look for a name through the MRO. + This returns a borrowed reference, and doesn't set an exception! */ +PyObject * +_PepType_Lookup(PyTypeObject *type, PyObject *name) +{ + PyObject *res; + int error; + + /* We may end up clearing live exceptions below, so make sure it's ours. */ + assert(!PyErr_Occurred()); + + res = find_name_in_mro(type, name, &error); + /* Only put NULL results into cache if there was no error. */ + if (error) { + /* It's not ideal to clear the error condition, + but this function is documented as not setting + an exception, and I don't want to change that. + E.g., when PyType_Ready() can't proceed, it won't + set the "ready" flag, so future attempts to ready + the same type will call it again -- hopefully + in a context that propagates the exception out. + */ + if (error == -1) { + PyErr_Clear(); + } + return nullptr; + } + return res; +} + +#endif // Py_LIMITED_API + +/***************************************************************************** + * * Support for unicodeobject.h * */ diff --git a/sources/shiboken2/libshiboken/pep384impl.h b/sources/shiboken2/libshiboken/pep384impl.h index 7a6f57fcd..07f4a913f 100644 --- a/sources/shiboken2/libshiboken/pep384impl.h +++ b/sources/shiboken2/libshiboken/pep384impl.h @@ -142,6 +142,12 @@ typedef struct _typeobject { LIBSHIBOKEN_API int PyIndex_Check(PyObject *obj); #endif +LIBSHIBOKEN_API PyObject *_PepType_Lookup(PyTypeObject *type, PyObject *name); + +#else // Py_LIMITED_API + +#define _PepType_Lookup(type, name) _PyType_Lookup(type, name) + #endif // Py_LIMITED_API struct SbkObjectTypePrivate; diff --git a/sources/shiboken2/libshiboken/sbkstaticstrings.h b/sources/shiboken2/libshiboken/sbkstaticstrings.h index b72fa989b..16d36041e 100644 --- a/sources/shiboken2/libshiboken/sbkstaticstrings.h +++ b/sources/shiboken2/libshiboken/sbkstaticstrings.h @@ -69,6 +69,7 @@ LIBSHIBOKEN_API PyObject *dict(); LIBSHIBOKEN_API PyObject *doc(); LIBSHIBOKEN_API PyObject *ecf(); LIBSHIBOKEN_API PyObject *file(); +LIBSHIBOKEN_API PyObject *func(); LIBSHIBOKEN_API PyObject *get(); LIBSHIBOKEN_API PyObject *members(); LIBSHIBOKEN_API PyObject *module(); diff --git a/sources/shiboken2/libshiboken/sbkstaticstrings_p.h b/sources/shiboken2/libshiboken/sbkstaticstrings_p.h index c33fa0299..acfc71409 100644 --- a/sources/shiboken2/libshiboken/sbkstaticstrings_p.h +++ b/sources/shiboken2/libshiboken/sbkstaticstrings_p.h @@ -60,7 +60,6 @@ PyObject *bases(); PyObject *builtins(); PyObject *code(); PyObject *dictoffset(); -PyObject *func(); PyObject *func_kind(); PyObject *iter(); PyObject *module(); |