diff options
Diffstat (limited to 'sources/pyside6/libpyside/feature_select.cpp')
-rw-r--r-- | sources/pyside6/libpyside/feature_select.cpp | 339 |
1 files changed, 182 insertions, 157 deletions
diff --git a/sources/pyside6/libpyside/feature_select.cpp b/sources/pyside6/libpyside/feature_select.cpp index d6a6300d7..cfd465267 100644 --- a/sources/pyside6/libpyside/feature_select.cpp +++ b/sources/pyside6/libpyside/feature_select.cpp @@ -89,18 +89,14 @@ This is everything that the following code does. *****************************************************************************/ -namespace PySide { namespace Feature { +namespace PySide::Feature { using namespace Shiboken; -typedef bool(*FeatureProc)(PyTypeObject *type, PyObject *prev_dict, int id); +using FeatureProc = bool(*)(PyTypeObject *type, PyObject *prev_dict, int id); static FeatureProc *featurePointer = nullptr; -static PyObject *_fast_id_array[1 + 256] = {}; -// this will point to element 1 to allow indexing from -1 -static PyObject **fast_id_array; - // Create a derived dict class static PyTypeObject * createDerivedDictType() @@ -111,7 +107,7 @@ createDerivedDictType() PyObject *ChameleonDict = PepRun_GetResult(R"CPP(if True: class ChameleonDict(dict): - __slots__ = ("dict_ring", "select_id") + __slots__ = ("dict_ring", "select_id", "orig_dict") result = ChameleonDict @@ -133,43 +129,30 @@ static void ensureNewDictType() static inline PyObject *nextInCircle(PyObject *dict) { // returns a borrowed ref - AutoDecRef next_dict(PyObject_GetAttr(dict, PyName::dict_ring())); + AutoDecRef next_dict(PyObject_GetAttr(dict, PySideName::dict_ring())); return next_dict; } static inline void setNextDict(PyObject *dict, PyObject *next_dict) { - PyObject_SetAttr(dict, PyName::dict_ring(), next_dict); -} - -static inline void setSelectId(PyObject *dict, PyObject *select_id) -{ - PyObject_SetAttr(dict, PyName::select_id(), select_id); -} - -static inline PyObject *getSelectId(PyObject *dict) -{ - auto select_id = PyObject_GetAttr(dict, PyName::select_id()); - return select_id; -} - -static inline void setCurrentSelectId(PyTypeObject *type, PyObject *select_id) -{ - SbkObjectType_SetReserved(type, PyLong_AsSsize_t(select_id)); // int/long cheating + PyObject_SetAttr(dict, PySideName::dict_ring(), next_dict); } -static inline void setCurrentSelectId(PyTypeObject *type, int id) +static inline void setSelectId(PyObject *dict, int select_id) { - SbkObjectType_SetReserved(type, id); + PyObject_SetAttr(dict, PySideName::select_id(), PyLong_FromLong(select_id)); } -static inline PyObject *getCurrentSelectId(PyTypeObject *type) +static inline int getSelectId(PyObject *dict) { - int id = SbkObjectType_GetReserved(type); - // This can be too early. - if (id < 0) - id = 0; - return fast_id_array[id]; + auto *py_select_id = PyObject_GetAttr(dict, PyName::select_id()); + if (py_select_id == nullptr) { + PyErr_Clear(); + return 0; + } + int ret = PyLong_AsLong(py_select_id); + Py_DECREF(py_select_id); + return ret; } static bool replaceClassDict(PyTypeObject *type) @@ -179,33 +162,32 @@ static bool replaceClassDict(PyTypeObject *type) * This is mandatory for all type dicts when they are touched. */ ensureNewDictType(); - PyObject *dict = type->tp_dict; - auto ob_ndt = reinterpret_cast<PyObject *>(new_dict_type); - PyObject *new_dict = PyObject_CallObject(ob_ndt, nullptr); + AutoDecRef dict(PepType_GetDict(type)); + auto *ob_ndt = reinterpret_cast<PyObject *>(new_dict_type); + auto *new_dict = PyObject_CallObject(ob_ndt, nullptr); if (new_dict == nullptr || PyDict_Update(new_dict, dict) < 0) return false; // Insert the default id. Cannot fail for small numbers. - AutoDecRef select_id(PyLong_FromLong(0)); - setSelectId(new_dict, select_id); + setSelectId(new_dict, 0); // insert the dict into itself as ring setNextDict(new_dict, new_dict); // We have now an exact copy of the dict with a new type. - // Replace `__dict__` which usually has refcount 1 (but see cyclic_test.py) - Py_DECREF(type->tp_dict); - type->tp_dict = new_dict; - setCurrentSelectId(type, select_id.object()); + PepType_SetDict(type, new_dict); + // PYSIDE-2404: Retain the original dict for easy late init. + PyObject_SetAttr(new_dict, PySideName::orig_dict(), dict); return true; } -static bool addNewDict(PyTypeObject *type, PyObject *select_id) +static bool addNewDict(PyTypeObject *type, int select_id) { /* * Add a new dict to the ring and set it as `type->tp_dict`. * A 'false' return is fatal. */ - auto dict = type->tp_dict; - auto ob_ndt = reinterpret_cast<PyObject *>(new_dict_type); - auto new_dict = PyObject_CallObject(ob_ndt, nullptr); + AutoDecRef dict(PepType_GetDict(type)); + AutoDecRef orig_dict(PyObject_GetAttr(dict, PySideName::orig_dict())); + auto *ob_ndt = reinterpret_cast<PyObject *>(new_dict_type); + auto *new_dict = PyObject_CallObject(ob_ndt, nullptr); if (new_dict == nullptr) return false; setSelectId(new_dict, select_id); @@ -213,35 +195,35 @@ static bool addNewDict(PyTypeObject *type, PyObject *select_id) auto next_dict = nextInCircle(dict); setNextDict(dict, new_dict); setNextDict(new_dict, next_dict); - type->tp_dict = new_dict; - setCurrentSelectId(type, select_id); + PepType_SetDict(type, new_dict); + // PYSIDE-2404: Retain the original dict for easy late init. + PyObject_SetAttr(new_dict, PySideName::orig_dict(), orig_dict); return true; } -static bool moveToFeatureSet(PyTypeObject *type, PyObject *select_id) +static inline bool moveToFeatureSet(PyTypeObject *type, int select_id) { /* * Rotate the ring to the given `select_id` and return `true`. * If not found, stay at the current position and return `false`. */ - auto initial_dict = type->tp_dict; - auto dict = initial_dict; + AutoDecRef tpDict(PepType_GetDict(type)); + auto *initial_dict = tpDict.object(); + auto *dict = initial_dict; do { - dict = nextInCircle(dict); - AutoDecRef current_id(getSelectId(dict)); + int current_id = getSelectId(dict); // This works because small numbers are singleton objects. if (current_id == select_id) { - type->tp_dict = dict; - setCurrentSelectId(type, select_id); + PepType_SetDict(type, dict); return true; } + dict = nextInCircle(dict); } while (dict != initial_dict); - type->tp_dict = initial_dict; - setCurrentSelectId(type, getSelectId(initial_dict)); + PepType_SetDict(type, initial_dict); return false; } -static bool createNewFeatureSet(PyTypeObject *type, PyObject *select_id) +static bool createNewFeatureSet(PyTypeObject *type, int select_id) { /* * Create a new feature set. @@ -251,37 +233,29 @@ static bool createNewFeatureSet(PyTypeObject *type, PyObject *select_id) * content in `prev_dict`. It is responsible of filling `type->tp_dict` * with modified content. */ - static auto small_1 = PyLong_FromLong(255); - Q_UNUSED(small_1); - static auto small_2 = PyLong_FromLong(255); - Q_UNUSED(small_2); - // make sure that small integers are cached - assert(small_1 != nullptr && small_1 == small_2); - - static auto zero = fast_id_array[0]; - bool ok = moveToFeatureSet(type, zero); + + bool ok = moveToFeatureSet(type, 0); Q_UNUSED(ok); assert(ok); - AutoDecRef prev_dict(type->tp_dict); - Py_INCREF(prev_dict); // keep the first ref unchanged + AutoDecRef prev_dict(PepType_GetDict(type)); if (!addNewDict(type, select_id)) return false; - auto id = PyLong_AsSsize_t(select_id); // int/long cheating + int id = select_id; if (id == -1) return false; - setCurrentSelectId(type, id); FeatureProc *proc = featurePointer; for (int idx = id; *proc != nullptr; ++proc, idx >>= 1) { if (idx & 1) { // clear the tp_dict that will get new content - PyDict_Clear(type->tp_dict); + AutoDecRef tpDict(PepType_GetDict(type)); + PyDict_Clear(tpDict); // let the proc re-fill the tp_dict if (!(*proc)(type, prev_dict, id)) return false; // if there is still a step, prepare `prev_dict` if (idx >> 1) { - prev_dict.reset(PyDict_Copy(type->tp_dict)); + prev_dict.reset(PyDict_Copy(tpDict.object())); if (prev_dict.isNull()) return false; } @@ -290,30 +264,59 @@ static bool createNewFeatureSet(PyTypeObject *type, PyObject *select_id) return true; } -static bool SelectFeatureSetSubtype(PyTypeObject *type, PyObject *select_id) +static inline void SelectFeatureSetSubtype(PyTypeObject *type, int select_id) { /* * This is the selector for one sublass. We need to call this for * every subclass until no more subclasses or reaching the wanted id. */ - if (Py_TYPE(type->tp_dict) == Py_TYPE(PyType_Type.tp_dict)) { + static const auto *pyTypeType_tp_dict = PepType_GetDict(&PyType_Type); + AutoDecRef tpDict(PepType_GetDict(type)); + if (Py_TYPE(tpDict.object()) == Py_TYPE(pyTypeType_tp_dict)) { // On first touch, we initialize the dynamic naming. // The dict type will be replaced after the first call. if (!replaceClassDict(type)) { Py_FatalError("failed to replace class dict!"); - return false; + return; } } if (!moveToFeatureSet(type, select_id)) { if (!createNewFeatureSet(type, select_id)) { Py_FatalError("failed to create a new feature set!"); - return false; + return; } } - return true; -} + } + +static PyObject *cached_globals{}; +static int last_select_id{}; -static inline PyObject *SelectFeatureSet(PyTypeObject *type) +static inline int getFeatureSelectId() +{ + static auto *undef = PyLong_FromLong(-1); + static auto *feature_dict = GetFeatureDict(); + // these things are all borrowed + auto *globals = PyEval_GetGlobals(); + if (globals == nullptr + || globals == cached_globals) + return last_select_id; + + auto *modname = PyDict_GetItem(globals, PyMagicName::name()); + if (modname == nullptr) + return last_select_id; + + auto *py_select_id = PyDict_GetItem(feature_dict, modname); + if (py_select_id == nullptr + || !PyLong_Check(py_select_id) + || py_select_id == undef) + return last_select_id; + + cached_globals = globals; + last_select_id = PyLong_AsLong(py_select_id) & 0xff; + return last_select_id; +} + +static inline void SelectFeatureSet(PyTypeObject *type) { /* * This is the main function of the module. @@ -323,33 +326,35 @@ static inline PyObject *SelectFeatureSet(PyTypeObject *type) * Generated functions call this directly. * Shiboken will assign it via a public hook of `basewrapper.cpp`. */ - if (Py_TYPE(type->tp_dict) == Py_TYPE(PyType_Type.tp_dict)) { + static const auto *pyTypeType_tp_dict = PepType_GetDict(&PyType_Type); + AutoDecRef tpDict(PepType_GetDict(type)); + if (Py_TYPE(tpDict.object()) == Py_TYPE(pyTypeType_tp_dict)) { // We initialize the dynamic features by using our own dict type. - if (!replaceClassDict(type)) - return nullptr; - } - PyObject *select_id = getFeatureSelectId(); // borrowed - PyObject *current_id = getCurrentSelectId(type); // borrowed - static PyObject *undef = fast_id_array[-1]; - - // PYSIDE-1019: During import PepType_SOTP is still zero. - if (current_id == undef) - current_id = select_id = fast_id_array[0]; - - if (select_id != current_id) { - PyObject *mro = type->tp_mro; - Py_ssize_t idx, n = PyTuple_GET_SIZE(mro); - // We leave 'Shiboken.Object' and 'object' alone, therefore "n - 2". - for (idx = 0; idx < n - 2; idx++) { - auto *sub_type = reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(mro, idx)); - // When any subtype is already resolved (false), we can stop. - if (!SelectFeatureSetSubtype(sub_type, select_id)) - break; + if (!replaceClassDict(type)) { + Py_FatalError("failed to replace class dict!"); + return; } - // PYSIDE-1436: Clear all caches for the type and subtypes. - PyType_Modified(type); } - return type->tp_dict; + + int select_id = getFeatureSelectId(); + static int last_select_id{}; + static PyTypeObject *last_type{}; + + // PYSIDE-2029: Implement a very simple but effective cache that cannot fail. + if (type == last_type && select_id == last_select_id) + return; + last_type = type; + last_select_id = select_id; + + auto *mro = type->tp_mro; + Py_ssize_t idx, n = PyTuple_GET_SIZE(mro); + // We leave 'Shiboken.Object' and 'object' alone, therefore "n - 2". + for (idx = 0; idx < n - 2; idx++) { + auto *sub_type = reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(mro, idx)); + SelectFeatureSetSubtype(sub_type, select_id); + } + // PYSIDE-1436: Clear all caches for the type and subtypes. + PyType_Modified(type); } // For cppgenerator: @@ -357,15 +362,14 @@ void Select(PyObject *obj) { if (featurePointer == nullptr) return; - auto type = Py_TYPE(obj); - type->tp_dict = SelectFeatureSet(type); + auto *type = Py_TYPE(obj); + SelectFeatureSet(type); } -PyObject *Select(PyTypeObject *type) +void Select(PyTypeObject *type) { if (featurePointer != nullptr) - type->tp_dict = SelectFeatureSet(type); - return type->tp_dict; + SelectFeatureSet(type); } static bool feature_01_addLowerNames(PyTypeObject *type, PyObject *prev_dict, int id); @@ -389,30 +393,27 @@ static FeatureProc featureProcArray[] = { nullptr }; -void finalize() -{ - for (int idx = -1; idx < 256; ++idx) - Py_DECREF(fast_id_array[idx]); -} - static bool patch_property_impl(); static bool is_initialized = false; +static void featureEnableCallback(bool enable) +{ + featurePointer = enable ? featureProcArray : nullptr; +} + void init() { // This function can be called multiple times. if (!is_initialized) { - fast_id_array = &_fast_id_array[1]; - for (int idx = -1; idx < 256; ++idx) - fast_id_array[idx] = PyLong_FromLong(idx); featurePointer = featureProcArray; initSelectableFeature(SelectFeatureSet); - registerCleanupFunction(finalize); + setSelectableFeatureCallback(featureEnableCallback); patch_property_impl(); is_initialized = true; } + last_select_id = 0; // Reset the cache. This is called at any "from __feature__ import". - initFeatureShibokenPart(); + cached_globals = nullptr; } void Enable(bool enable) @@ -442,7 +443,7 @@ static PyObject *methodWithNewName(PyTypeObject *type, /* * Create a method with a lower case name. */ - auto obtype = reinterpret_cast<PyObject *>(type); + auto *obtype = reinterpret_cast<PyObject *>(type); int len = strlen(new_name); auto name = new char[len + 1]; strcpy(name, new_name); @@ -464,10 +465,11 @@ static PyObject *methodWithNewName(PyTypeObject *type, return descr; } -static bool feature_01_addLowerNames(PyTypeObject *type, PyObject *prev_dict, int id) +static bool feature_01_addLowerNames(PyTypeObject *type, PyObject *prev_dict, int /* id */) { PyMethodDef *meth = type->tp_methods; - PyObject *lower_dict = type->tp_dict; + AutoDecRef tpDict(PepType_GetDict(type)); + PyObject *lower_dict = tpDict.object(); // PYSIDE-1702: A user-defined class in Python has no internal method list. // We are not going to change anything. @@ -514,20 +516,20 @@ static bool feature_01_addLowerNames(PyTypeObject *type, PyObject *prev_dict, in // This is the Python 2 version for inspection of m_ml, only. // The actual Python 3 version is larget. -typedef struct { +struct PyCFunctionObject { 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; + 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; @@ -541,26 +543,26 @@ static PyObject *createProperty(PyTypeObject *type, PyObject *getter, PyObject * assert(getter != nullptr); if (setter == nullptr) setter = Py_None; - auto ptype = &PyProperty_Type; + auto *ptype = &PyProperty_Type; if (Py_TYPE(getter) == PepStaticMethod_TypePtr) { ptype = PyClassProperty_TypeF(); getter = modifyStaticToClassMethod(type, getter); if (setter != Py_None) setter = modifyStaticToClassMethod(type, setter); } - auto obtype = reinterpret_cast<PyObject *>(ptype); + auto *obtype = reinterpret_cast<PyObject *>(ptype); PyObject *prop = PyObject_CallFunctionObjArgs(obtype, getter, setter, nullptr); return prop; } -static QStringList parseFields(const char *propstr, bool *stdwrite) +static const QByteArrayList parseFields(const char *propStr, bool *stdWrite) { /* * Break the string into subfields at ':' and add defaults. */ - if (stdwrite) - *stdwrite = true; - QString s = QString(QLatin1String(propstr)); + if (stdWrite) + *stdWrite = true; + QByteArray s = QByteArray(propStr); auto list = s.split(u':'); assert(list.size() == 2 || list.size() == 3); auto name = list[0]; @@ -570,20 +572,20 @@ static QStringList parseFields(const char *propstr, bool *stdwrite) if (list.size() == 2) return list; auto write = list[2]; - if (stdwrite) - *stdwrite = write.isEmpty(); + if (stdWrite) + *stdWrite = write.isEmpty(); if (write.isEmpty()) { - list[2] = QLatin1String("set") + name; - list[2][3] = list[2][3].toUpper(); + list[2] = "set" + name; + list[2][3] = std::toupper(list[2][3]); } return list; } -static PyObject *make_snake_case(QString s, bool lower) +static PyObject *make_snake_case(const QByteArray &s, bool lower) { if (s.isNull()) return nullptr; - return String::getSnakeCaseName(s.toLatin1().data(), lower); + return String::getSnakeCaseName(s.constData(), lower); } PyObject *adjustPropertyName(PyObject *dict, PyObject *name) @@ -598,7 +600,7 @@ PyObject *adjustPropertyName(PyObject *dict, PyObject *name) if (PyList_CheckExact(sig)) { name_clash = true; } else { - Shiboken::AutoDecRef params(PyObject_GetAttr(sig, PyName::parameters())); + Shiboken::AutoDecRef params(PyObject_GetAttr(sig, PySideName::parameters())); // Are there parameters except self or cls? if (PyObject_Size(params.object()) > 1) name_clash = true; @@ -614,6 +616,28 @@ PyObject *adjustPropertyName(PyObject *dict, PyObject *name) return name; } +static QByteArrayList GetPropertyStringsMro(PyTypeObject *type) +{ + /* + * PYSIDE-2042: There are possibly more methods which should become properties, + * because the wrapping process does not obey inheritance. + * Therefore, we need to walk the mro to find property strings. + */ + auto res = QByteArrayList(); + + PyObject *mro = type->tp_mro; + const Py_ssize_t n = PyTuple_GET_SIZE(mro); + // We leave 'Shiboken.Object' and 'object' alone, therefore "n - 2". + for (Py_ssize_t idx = 0; idx < n - 2; idx++) { + auto *subType = reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(mro, idx)); + auto props = SbkObjectType_GetPropertyStrings(subType); + if (props != nullptr) + for (; *props != nullptr; ++props) + res << QByteArray(*props); + } + return res; +} + static bool feature_02_true_property(PyTypeObject *type, PyObject *prev_dict, int id) { /* @@ -621,7 +645,8 @@ static bool feature_02_true_property(PyTypeObject *type, PyObject *prev_dict, in */ PyMethodDef *meth = type->tp_methods; - PyObject *prop_dict = type->tp_dict; + AutoDecRef tpDict(PepType_GetDict(type)); + PyObject *prop_dict = tpDict.object(); // The empty `tp_dict` gets populated by the previous dict. if (PyDict_Update(prop_dict, prev_dict) < 0) @@ -643,13 +668,13 @@ static bool feature_02_true_property(PyTypeObject *type, PyObject *prev_dict, in } // We then replace methods by properties. bool lower = (id & 0x01) != 0; - auto props = SbkObjectType_GetPropertyStrings(type); - if (props == nullptr || *props == nullptr) + auto props = GetPropertyStringsMro(type); + if (props.isEmpty()) return true; - for (; *props != nullptr; ++props) { + + for (const auto &propStr : std::as_const(props)) { bool isStdWrite; - auto propstr = *props; - auto fields = parseFields(propstr, &isStdWrite); + auto fields = parseFields(propStr, &isStdWrite); bool haveWrite = fields.size() == 3; PyObject *name = make_snake_case(fields[0], lower); PyObject *read = make_snake_case(fields[1], lower); @@ -695,7 +720,7 @@ static bool feature_02_true_property(PyTypeObject *type, PyObject *prev_dict, in static PyObject *property_doc_get(PyObject *self, void *) { - auto po = reinterpret_cast<propertyobject *>(self); + auto *po = reinterpret_cast<propertyobject *>(self); if (po->prop_doc != nullptr && po->prop_doc != Py_None) { Py_INCREF(po->prop_doc); @@ -703,7 +728,7 @@ static PyObject *property_doc_get(PyObject *self, void *) } if (po->prop_get) { // PYSIDE-1019: Fetch the default `__doc__` from fget. We do it late. - auto txt = PyObject_GetAttr(po->prop_get, PyMagicName::doc()); + auto *txt = PyObject_GetAttr(po->prop_get, PyMagicName::doc()); if (txt != nullptr) { Py_INCREF(txt); po->prop_doc = txt; @@ -717,7 +742,7 @@ static PyObject *property_doc_get(PyObject *self, void *) static int property_doc_set(PyObject *self, PyObject *value, void *) { - auto po = reinterpret_cast<propertyobject *>(self); + auto *po = reinterpret_cast<propertyobject *>(self); Py_INCREF(value); po->prop_doc = value; @@ -734,12 +759,12 @@ static bool patch_property_impl() { // Turn `__doc__` into a computed attribute without changing writability. auto gsp = property_getset; - auto type = &PyProperty_Type; - auto dict = type->tp_dict; + auto *type = &PyProperty_Type; + AutoDecRef dict(PepType_GetDict(type)); AutoDecRef descr(PyDescr_NewGetSet(type, gsp)); if (descr.isNull()) return false; - if (PyDict_SetItemString(dict, gsp->name, descr) < 0) + if (PyDict_SetItemString(dict.object(), gsp->name, descr) < 0) return false; return true; } @@ -748,13 +773,14 @@ static bool patch_property_impl() // // PYSIDE-1019: Support switchable extensions // -// Feature 0x04..0x40: A fake switchable option for testing +// Feature 0x04..0x80: A fake switchable option for testing // #define SIMILAR_FEATURE(xx) \ -static bool feature_##xx##_addDummyNames(PyTypeObject *type, PyObject *prev_dict, int id) \ +static bool feature_##xx##_addDummyNames(PyTypeObject *type, PyObject *prev_dict, int /* id */) \ { \ - PyObject *dict = type->tp_dict; \ + AutoDecRef tpDict(PepType_GetDict(type)); \ + PyObject *dict = tpDict.object(); \ if (PyDict_Update(dict, prev_dict) < 0) \ return false; \ if (PyDict_SetItemString(dict, "fake_feature_" #xx, Py_None) < 0) \ @@ -769,5 +795,4 @@ SIMILAR_FEATURE(20) SIMILAR_FEATURE(40) SIMILAR_FEATURE(80) -} // namespace PySide -} // namespace Feature +} // namespace PySide::Feature |