// Copyright (C) 2020 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "class_property.h" #include "pysidestaticstrings.h" #include "feature_select.h" #include #include #include 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_descr_get(PyObject *self, PyObject * /*ob*/, PyObject *cls) { return PepExt_Type_GetDescrGetSlot(&PyProperty_Type)(self, cls, cls); } // `class_property.__set__()`: Just like the above `__get__()`. static int PyClassProperty_descr_set(PyObject *self, PyObject *obj, PyObject *value) { PyObject *cls = PyType_Check(obj) ? obj : reinterpret_cast(Py_TYPE(obj)); return PepExt_Type_GetDescrSetSlot(&PyProperty_Type)(self, cls, value); } // PYSIDE-2230: Why is this metaclass necessary? // // The problem is that the property object already exists as a Python // object. We derive a subclass for class properties, without // repeating everything but just by adding something to support // the class-ness. // // But this Python property has as metaclass `type` which is incompatible // now with SbkObjectType, which generates physically larger types that // are incompatible with properties by using PEP 697. // Adding a compatible metaclass that is unrelated to `SbkObjectType` // is the correct solution. Re-using `SbkObjectType` was actually an abuse, // since Python properties are in no way PySide objects. static PyTypeObject *createClassPropertyTypeType() { PyType_Slot PyClassPropertyType_Type_slots[] = { {Py_tp_base, static_cast(&PyType_Type)}, {Py_tp_alloc, reinterpret_cast(PyType_GenericAlloc)}, {Py_tp_free, reinterpret_cast(PyObject_GC_Del)}, {0, nullptr} }; PyType_Spec PyClassPropertyType_Type_spec = { "1:Shiboken.ClassPropertyType", 0, 0, // sizeof(PyMemberDef), not for PyPy without a __len__ defined Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_TYPE_SUBCLASS, PyClassPropertyType_Type_slots, }; return SbkType_FromSpec(&PyClassPropertyType_Type_spec); } PyTypeObject *PyClassPropertyType_TypeF() { static auto *type = createClassPropertyTypeType(); return type; } // 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_tp_init(PyObject *self, PyObject *args, PyObject *kwargs) { auto hold = Py_TYPE(self); self->ob_type = &PyProperty_Type; auto ret = PepExt_Type_GetInitSlot(&PyProperty_Type)(self, args, kwargs); self->ob_type = hold; return ret; } static PyTypeObject *createPyClassPropertyType() { PyType_Slot PyClassProperty_slots[] = { {Py_tp_getset, reinterpret_cast(PyProperty_Type.tp_getset)}, // will be set below {Py_tp_base, reinterpret_cast(&PyProperty_Type)}, {Py_tp_descr_get, reinterpret_cast(PyClassProperty_descr_get)}, {Py_tp_descr_set, reinterpret_cast(PyClassProperty_descr_set)}, {Py_tp_init, reinterpret_cast(PyClassProperty_tp_init)}, {0, nullptr} }; PyType_Spec PyClassProperty_spec = { "2:PySide6.QtCore.PyClassProperty", sizeof(propertyobject), 0, Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, PyClassProperty_slots, }; if (_PepRuntimeVersion() >= 0x030A00) PyClassProperty_spec.basicsize = sizeof(propertyobject310); return SbkType_FromSpecWithMeta(&PyClassProperty_spec, PyClassPropertyType_TypeF()); } PyTypeObject *PyClassProperty_TypeF() { static auto *type = createPyClassPropertyType(); 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(obj); PySide::Feature::Select(type); 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(PyClassProperty_TypeF()); 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 PepExt_Type_GetDescrSetSlot(Py_TYPE(descr))(descr, obj, value); } // Replace existing attribute. return PepExt_Type_GetSetAttroSlot(&PyType_Type)(obj, name, value); } } // extern "C" /* * These functions are added to the SbkObjectType_TypeF() dynamically. */ namespace PySide::ClassProperty { static const char *PyClassProperty_SignatureStrings[] = { "PySide6.QtCore.PyClassProperty(cls," "fget:typing.Optional[typing.Callable[[typing.Any],typing.Any]]=None," "fset:typing.Optional[typing.Callable[[typing.Any,typing.Any],None]]=None," "fdel:typing.Optional[typing.Callable[[typing.Any],None]]=None," "doc:typing.Optional[str]=None)", "PySide6.QtCore.PyClassProperty.getter(cls,fget:typing.Callable[[typing.Any],typing.Any])->PySide6.QtCore.PyClassProperty", "PySide6.QtCore.PyClassProperty.setter(cls,fset:typing.Callable[[typing.Any,typing.Any],None])->PySide6.QtCore.PyClassProperty", "PySide6.QtCore.PyClassProperty.deleter(cls,fdel:typing.Callable[[typing.Any],None])->PySide6.QtCore.PyClassProperty", nullptr}; // Sentinel void init(PyObject *module) { PyTypeObject *type = SbkObjectType_TypeF(); type->tp_setattro = SbkObjectType_meta_setattro; if (InitSignatureStrings(PyClassProperty_TypeF(), PyClassProperty_SignatureStrings) < 0) return; Py_INCREF(PyClassProperty_TypeF()); auto classproptype = reinterpret_cast(PyClassProperty_TypeF()); PyModule_AddObject(module, "PyClassProperty", classproptype); } } // namespace PySide::ClassProperty