diff options
28 files changed, 661 insertions, 250 deletions
diff --git a/sources/pyside6/doc/building_from_source/index.rst b/sources/pyside6/doc/building_from_source/index.rst index 3899f4361..a20143726 100644 --- a/sources/pyside6/doc/building_from_source/index.rst +++ b/sources/pyside6/doc/building_from_source/index.rst @@ -484,7 +484,7 @@ The target executes several steps: #. ``sphinx`` is run to produce the documentation in HTML format. Re-running the command will not execute step 1 unless the file -``qdoc-output/webxml/qtcore-index.webxml`` is removed from the build tree. +``qdoc-output/qtcore/webxml/qtcore-index.webxml`` is removed from the build tree. Similarly, step 2 will not be executed unless the file ``base/PySide6/QtCore/index.rst`` is removed. diff --git a/sources/pyside6/libpyside/CMakeLists.txt b/sources/pyside6/libpyside/CMakeLists.txt index 6aa4d7580..7640d1cce 100644 --- a/sources/pyside6/libpyside/CMakeLists.txt +++ b/sources/pyside6/libpyside/CMakeLists.txt @@ -29,6 +29,7 @@ set(libpyside_HEADERS # installed below pyside_p.h pysideglobals_p.h pysideproperty.h + pysidepropertybase_p.h pysideproperty_p.h pysideqapp.h pysideqenum.h diff --git a/sources/pyside6/libpyside/dynamicqmetaobject.cpp b/sources/pyside6/libpyside/dynamicqmetaobject.cpp index db8e0c5ae..46bc3ace9 100644 --- a/sources/pyside6/libpyside/dynamicqmetaobject.cpp +++ b/sources/pyside6/libpyside/dynamicqmetaobject.cpp @@ -275,7 +275,7 @@ void MetaObjectBuilder::removeMethod(QMetaMethod::MethodType mtype, int index) int MetaObjectBuilderPrivate::getPropertyNotifyId(PySideProperty *property) const { int notifyId = -1; - if (property->d->notify) { + if (property->d->notify()) { if (const char *signalNotify = PySide::Property::getNotifyName(property)) notifyId = indexOfMethod(QMetaMethod::Signal, signalNotify); } @@ -316,13 +316,14 @@ QMetaPropertyBuilder } } } - const auto metaType = QMetaType::fromName(property->d->typeName); + const QByteArray &typeName = property->d->typeName(); + const auto metaType = QMetaType::fromName(typeName); if (!metaType.isValid()) { const auto &msg = msgInvalidPropertyType(m_builder->className(), propertyName, - property->d->typeName); + typeName); PyErr_WarnEx(PyExc_RuntimeWarning, msg.constData(), 0); } - return builder->addProperty(propertyName, property->d->typeName, metaType, propertyNotifyId); + return builder->addProperty(propertyName, typeName, metaType, propertyNotifyId); } int MetaObjectBuilderPrivate::addProperty(const QByteArray &propertyName, @@ -336,7 +337,7 @@ int MetaObjectBuilderPrivate::addProperty(const QByteArray &propertyName, auto newProperty = createProperty(property, propertyName); // Adding property attributes - const auto &flags = property->d->flags; + const auto flags = property->d->flags(); newProperty.setReadable(flags.testFlag(PySide::Property::PropertyFlag::Readable)); newProperty.setWritable(flags.testFlag(PySide::Property::PropertyFlag::Writable)); newProperty.setResettable(flags.testFlag(PySide::Property::PropertyFlag::Resettable)); diff --git a/sources/pyside6/libpyside/pysideproperty.cpp b/sources/pyside6/libpyside/pysideproperty.cpp index 3d1968045..0207a7320 100644 --- a/sources/pyside6/libpyside/pysideproperty.cpp +++ b/sources/pyside6/libpyside/pysideproperty.cpp @@ -16,6 +16,8 @@ #include <sbktypefactory.h> #include <signature.h> +#include <utility> + using namespace Shiboken; using namespace Qt::StringLiterals; @@ -101,9 +103,40 @@ PyTypeObject *PySideProperty_TypeF(void) return type; } +PySidePropertyBase::PySidePropertyBase(Type t) : m_type(t) +{ +} + +PySidePropertyBase::PySidePropertyBase(const PySidePropertyBase &rhs) = default; + +void PySidePropertyBase::tp_clearBase() +{ + Py_CLEAR(m_notify); + Py_CLEAR(m_pyTypeObject); +} + +int PySidePropertyBase::tp_traverseBase(visitproc visit, void *arg) +{ + Py_VISIT(m_notify); + Py_VISIT(m_pyTypeObject); + return 0; +} + +void PySidePropertyBase::increfBase() +{ + Py_XINCREF(m_notify); + Py_XINCREF(m_pyTypeObject); +} + +PySidePropertyBase *PySidePropertyBase::clone() const +{ + Q_UNIMPLEMENTED(); + return nullptr; +} + // Helper to check a callable function passed to a property instance. -bool PySidePropertyPrivate::assignCheckCallable(PyObject *source, const char *name, - PyObject **target) +bool PySidePropertyBase::assignCheckCallable(PyObject *source, const char *name, + PyObject **target) { if (source != nullptr && source != Py_None) { if (PyCallable_Check(source) == 0) { @@ -117,8 +150,32 @@ bool PySidePropertyPrivate::assignCheckCallable(PyObject *source, const char *na return true; } -PySidePropertyPrivate::PySidePropertyPrivate() noexcept = default; -PySidePropertyPrivate::~PySidePropertyPrivate() = default; +void PySidePropertyPrivate::tp_clear() +{ + PySidePropertyBase::tp_clearBase(); + Py_CLEAR(fget); + Py_CLEAR(fset); + Py_CLEAR(freset); + Py_CLEAR(fdel); +} + +int PySidePropertyPrivate::tp_traverse(visitproc visit, void *arg) +{ + Py_VISIT(fget); + Py_VISIT(fset); + Py_VISIT(freset); + Py_VISIT(fdel); + return PySidePropertyBase::tp_traverseBase(visit, arg); +} + +void PySidePropertyPrivate::incref() +{ + PySidePropertyBase::increfBase(); + Py_XINCREF(fget); + Py_XINCREF(fset); + Py_XINCREF(freset); + Py_XINCREF(fdel); +} PyObject *PySidePropertyPrivate::getValue(PyObject *source) const { @@ -165,6 +222,13 @@ int PySidePropertyPrivate::reset(PyObject *source) return -1; } +PySidePropertyPrivate *PySidePropertyPrivate::clone() const +{ + auto *result = new PySidePropertyPrivate(*this); + result->incref(); + return result; +} + void PySidePropertyPrivate::metaCall(PyObject *source, QMetaObject::Call call, void **args) { switch (call) { @@ -172,13 +236,13 @@ void PySidePropertyPrivate::metaCall(PyObject *source, QMetaObject::Call call, v AutoDecRef value(getValue(source)); if (value.isNull()) return; - if (typeName == "PyObject"_ba) { + if (typeName() == "PyObject"_ba) { // Manual conversion, see PyObjectWrapper converter registration auto *pw = reinterpret_cast<PySide::PyObjectWrapper *>(args[0]); pw->reset(value.object()); return; } - if (Conversions::SpecificConverter converter(typeName); converter) { + if (Conversions::SpecificConverter converter(typeName()); converter) { converter.toCpp(value.object(), args[0]); return; } @@ -188,7 +252,7 @@ void PySidePropertyPrivate::metaCall(PyObject *source, QMetaObject::Call call, v break; case QMetaObject::WriteProperty: { - Conversions::SpecificConverter converter(typeName); + Conversions::SpecificConverter converter(typeName()); if (converter) { AutoDecRef value(converter.toPython(args[0])); setValue(source, value); @@ -213,7 +277,7 @@ void PySidePropertyPrivate::metaCall(PyObject *source, QMetaObject::Call call, v static const char dataCapsuleName[] = "PropertyPrivate"; static const char dataCapsuleKeyName[] = "_PropertyPrivate"; // key in keyword args -static PySidePropertyPrivate *getDataFromKwArgs(PyObject *kwds) +static PySidePropertyBase *getDataFromKwArgs(PyObject *kwds) { if (kwds != nullptr && PyDict_Check(kwds) != 0) { static PyObject *key = PyUnicode_InternFromString(dataCapsuleKeyName); @@ -221,19 +285,27 @@ static PySidePropertyPrivate *getDataFromKwArgs(PyObject *kwds) Shiboken::AutoDecRef data(PyDict_GetItem(kwds, key)); if (PyCapsule_CheckExact(data.object()) != 0) { if (void *p = PyCapsule_GetPointer(data.object(), dataCapsuleName)) - return reinterpret_cast<PySidePropertyPrivate *>(p); + return reinterpret_cast<PySidePropertyBase *>(p); } } } return nullptr; } -static void addDataCapsuleToKwArgs(const AutoDecRef &kwds, PySidePropertyPrivate *data) +static void addDataCapsuleToKwArgs(const AutoDecRef &kwds, PySidePropertyBase *data) { auto *capsule = PyCapsule_New(data, dataCapsuleName, nullptr); PyDict_SetItemString(kwds.object(), dataCapsuleKeyName, capsule); } +static inline PySidePropertyPrivate *propertyPrivate(PyObject *self) +{ + auto *data = reinterpret_cast<PySideProperty *>(self); + Q_ASSERT(data->d != nullptr); + Q_ASSERT(data->d->type() == PySidePropertyBase::Type::Property); + return static_cast<PySidePropertyPrivate *>(data->d); +} + static PyObject *qpropertyTpNew(PyTypeObject *subtype, PyObject * /* args */, PyObject *kwds) { auto *me = PepExt_TypeCallAlloc<PySideProperty>(subtype, 0); @@ -245,8 +317,10 @@ static PyObject *qpropertyTpNew(PyTypeObject *subtype, PyObject * /* args */, Py static int qpropertyTpInit(PyObject *self, PyObject *args, PyObject *kwds) { - auto *data = reinterpret_cast<PySideProperty *>(self); - PySidePropertyPrivate *pData = data->d; + auto *pData = propertyPrivate(self); + + if (!pData->typeName().isEmpty()) // Cloned copy, already initialized + return 0; static const char *kwlist[] = {"type", "fget", "fset", "freset", "fdel", "doc", "notify", "designable", "scriptable", "stored", @@ -274,27 +348,24 @@ static int qpropertyTpInit(PyObject *self, PyObject *args, PyObject *kwds) || !PySidePropertyPrivate::assignCheckCallable(fset, "fset", &pData->fset) || !PySidePropertyPrivate::assignCheckCallable(freset, "freset", &pData->freset) || !PySidePropertyPrivate::assignCheckCallable(fdel, "fdel", &pData->fdel)) { - pData->fget = pData->fset = pData->freset = pData->fdel = pData->notify = nullptr; + pData->fget = pData->fset = pData->freset = pData->fdel = nullptr; + pData->setNotify(nullptr); return -1; } if (notify != nullptr && notify != Py_None) - pData->notify = notify; + pData->setNotify(notify); // PYSIDE-1019: Fetching the default `__doc__` from fget would fail for inherited functions // because we don't initialize the mro with signatures (and we will not!). // But it is efficient and in-time to do that on demand in qPropertyDocGet. pData->getter_doc = false; - if (doc) - pData->doc = doc; - else - pData->doc.clear(); + pData->setDoc(doc != nullptr ? QByteArray(doc) : QByteArray{}); - pData->pyTypeObject = type; - Py_XINCREF(pData->pyTypeObject); - pData->typeName = PySide::Signal::getTypeName(type); + pData->setPyTypeObject(type); + pData->setTypeName(PySide::Signal::getTypeName(type)); - auto &flags = pData->flags; + PySide::Property::PropertyFlags flags; flags.setFlag(PySide::Property::PropertyFlag::Readable, pData->fget != nullptr); flags.setFlag(PySide::Property::PropertyFlag::Writable, pData->fset != nullptr); flags.setFlag(PySide::Property::PropertyFlag::Resettable, pData->freset != nullptr); @@ -304,24 +375,22 @@ static int qpropertyTpInit(PyObject *self, PyObject *args, PyObject *kwds) flags.setFlag(PySide::Property::PropertyFlag::User, user); flags.setFlag(PySide::Property::PropertyFlag::Constant, constant); flags.setFlag(PySide::Property::PropertyFlag::Final, finalProp); + pData->setFlags(flags); - if (type == Py_None || pData->typeName.isEmpty()) + if (type == Py_None || pData->typeName().isEmpty()) PyErr_SetString(PyExc_TypeError, "Invalid property type or type name."); else if (constant && pData->fset != nullptr) PyErr_SetString(PyExc_TypeError, "A constant property cannot have a WRITE method."); - else if (constant && pData->notify != nullptr) + else if (constant && pData->notify() != nullptr) PyErr_SetString(PyExc_TypeError, "A constant property cannot have a NOTIFY signal."); if (PyErr_Occurred() != nullptr) { - pData->fget = pData->fset = pData->freset = pData->fdel = pData->notify = nullptr; + pData->fget = pData->fset = pData->freset = pData->fdel = nullptr; + pData->setNotify(nullptr); return -1; } - Py_XINCREF(pData->fget); - Py_XINCREF(pData->fset); - Py_XINCREF(pData->freset); - Py_XINCREF(pData->fdel); - Py_XINCREF(pData->notify); + pData->incref(); return 0; } @@ -336,88 +405,80 @@ static void qpropertyDeAlloc(PyObject *self) } // Create a copy of the property to prevent the @property.setter from modifying -// the property in place and avoid strange side effects in derived classes -// (cf https://bugs.python.org/issue1620). -static PyObject * -_property_copy(PyObject *old, PyObject *get, PyObject *set, PyObject *reset, PyObject *del) +// the property in place and avoid strange side effects when modifying the +// property in derived classes (cf https://bugs.python.org/issue1620, +// pysidetest/property_python_test.py). +static PyObject *copyProperty(PyObject *old) { - auto *pold = reinterpret_cast<PySideProperty *>(old); - PySidePropertyPrivate *pData = pold->d; - AutoDecRef type(PyObject_Type(old)); - QByteArray doc{}; - if (type.isNull()) - return nullptr; - - if (get == nullptr || get == Py_None) { - Py_XDECREF(get); - get = pData->fget ? pData->fget : Py_None; - } - if (set == nullptr || set == Py_None) { - Py_XDECREF(set); - set = pData->fset ? pData->fset : Py_None; - } - if (reset == nullptr || reset == Py_None) { - Py_XDECREF(reset); - reset = pData->freset ? pData->freset : Py_None; - } - if (del == nullptr || del == Py_None) { - Py_XDECREF(del); - del = pData->fdel ? pData->fdel : Py_None; - } - // make _init use __doc__ from getter - if ((pData->getter_doc && get != Py_None) || pData->doc.isEmpty()) - doc.clear(); - else - doc = pData->doc; - - auto *notify = pData->notify ? pData->notify : Py_None; - - const auto &flags = pData->flags; - PyObject *obNew = - PyObject_CallFunction(type, "OOOOOsO" "bbb" "bbb", - pData->pyTypeObject, get, set, reset, del, doc.data(), notify, - flags.testFlag(PySide::Property::PropertyFlag::Designable), - flags.testFlag(PySide::Property::PropertyFlag::Scriptable), - flags.testFlag(PySide::Property::PropertyFlag::Stored), - flags.testFlag(PySide::Property::PropertyFlag::User), - flags.testFlag(PySide::Property::PropertyFlag::Constant), - flags.testFlag(PySide::Property::PropertyFlag::Final)); - - return obNew; + Shiboken::AutoDecRef kwds(PyDict_New()); + addDataCapsuleToKwArgs(kwds, propertyPrivate(old)->clone()); + Shiboken::AutoDecRef args(PyTuple_New(0)); + return PyObject_Call(type.object(), args.object(), kwds.object()); } static PyObject *qPropertyGetter(PyObject *self, PyObject *getter) { - return _property_copy(self, getter, nullptr, nullptr, nullptr); + PyObject *result = copyProperty(self); + if (result != nullptr) { + auto *data = propertyPrivate(result); + auto *old = std::exchange(data->fget, getter); + Py_XINCREF(data->fget); + Py_XDECREF(old); + data->setFlag(PySide::Property::PropertyFlag::Readable); + } + return result; } static PyObject *qPropertySetter(PyObject *self, PyObject *setter) { - return _property_copy(self, nullptr, setter, nullptr, nullptr); + PyObject *result = copyProperty(self); + if (result != nullptr) { + auto *data = propertyPrivate(result); + auto *old = std::exchange(data->fset, setter); + Py_XINCREF(data->fset); + Py_XDECREF(old); + data->setFlag(PySide::Property::PropertyFlag::Writable); + } + return result; } static PyObject *qPropertyResetter(PyObject *self, PyObject *resetter) { - return _property_copy(self, nullptr, nullptr, resetter, nullptr); + PyObject *result = copyProperty(self); + if (result != nullptr) { + auto *data = propertyPrivate(result); + auto *old = std::exchange(data->freset, resetter); + Py_XINCREF(data->freset); + Py_XDECREF(old); + data->setFlag(PySide::Property::PropertyFlag::Resettable); + } + return result; } static PyObject *qPropertyDeleter(PyObject *self, PyObject *deleter) { - return _property_copy(self, nullptr, nullptr, nullptr, deleter); + PyObject *result = copyProperty(self); + if (result != nullptr) { + auto *data = propertyPrivate(result); + auto *old = std::exchange(data->fdel, deleter); + Py_XINCREF(data->fdel); + Py_XDECREF(old); + } + return result; } static PyObject *qPropertyCall(PyObject *self, PyObject *args, PyObject * /* kw */) { PyObject *getter = PyTuple_GetItem(args, 0); - return _property_copy(self, getter, nullptr, nullptr, nullptr); + return qPropertyGetter(self, getter); } // PYSIDE-1019: Provide the same getters as Pythons `PyProperty`. static PyObject *qProperty_fget(PyObject *self, void *) { - auto *func = reinterpret_cast<PySideProperty *>(self)->d->fget; + auto *func = propertyPrivate(self)->fget; if (func == nullptr) Py_RETURN_NONE; Py_INCREF(func); @@ -426,7 +487,7 @@ static PyObject *qProperty_fget(PyObject *self, void *) static PyObject *qProperty_fset(PyObject *self, void *) { - auto *func = reinterpret_cast<PySideProperty *>(self)->d->fset; + auto *func = propertyPrivate(self)->fset; if (func == nullptr) Py_RETURN_NONE; Py_INCREF(func); @@ -435,7 +496,7 @@ static PyObject *qProperty_fset(PyObject *self, void *) static PyObject *qProperty_freset(PyObject *self, void *) { - auto *func = reinterpret_cast<PySideProperty *>(self)->d->freset; + auto *func = propertyPrivate(self)->freset; if (func == nullptr) Py_RETURN_NONE; Py_INCREF(func); @@ -444,7 +505,7 @@ static PyObject *qProperty_freset(PyObject *self, void *) static PyObject *qProperty_fdel(PyObject *self, void *) { - auto *func = reinterpret_cast<PySideProperty *>(self)->d->fdel; + auto *func = propertyPrivate(self)->fdel; if (func == nullptr) Py_RETURN_NONE; Py_INCREF(func); @@ -454,16 +515,15 @@ static PyObject *qProperty_fdel(PyObject *self, void *) static PyObject *qPropertyDocGet(PyObject *self, void *) { auto *data = reinterpret_cast<PySideProperty *>(self); - PySidePropertyPrivate *pData = data->d; + if (!data->d->doc().isEmpty() || data->d->type() != PySidePropertyBase::Type::Property) + return PyUnicode_FromString(data->d->doc()); - QByteArray doc(pData->doc); - if (!doc.isEmpty()) - return PyUnicode_FromString(doc); + auto *pData = static_cast<PySidePropertyPrivate *>(data->d); if (pData->fget != nullptr) { // PYSIDE-1019: Fetch the default `__doc__` from fget. We do it late. AutoDecRef get_doc(PyObject_GetAttr(pData->fget, PyMagicName::doc())); if (!get_doc.isNull() && get_doc.object() != Py_None) { - pData->doc = String::toCString(get_doc); + pData->setDoc(String::toCString(get_doc)); pData->getter_doc = true; if (Py_TYPE(self) == PySideProperty_TypeF()) return qPropertyDocGet(self, nullptr); @@ -486,10 +546,8 @@ static PyObject *qPropertyDocGet(PyObject *self, void *) static int qPropertyDocSet(PyObject *self, PyObject *value, void *) { auto *data = reinterpret_cast<PySideProperty *>(self); - PySidePropertyPrivate *pData = data->d; - if (String::check(value)) { - pData->doc = String::toCString(value); + data->d->setDoc(String::toCString(value)); return 0; } PyErr_SetString(PyExc_TypeError, "String argument expected."); @@ -498,34 +556,20 @@ static int qPropertyDocSet(PyObject *self, PyObject *value, void *) static int qpropertyTraverse(PyObject *self, visitproc visit, void *arg) { - PySidePropertyPrivate *data = reinterpret_cast<PySideProperty *>(self)->d; - if (!data) - return 0; - - Py_VISIT(data->fget); - Py_VISIT(data->fset); - Py_VISIT(data->freset); - Py_VISIT(data->fdel); - Py_VISIT(data->notify); - Py_VISIT(data->pyTypeObject); - return 0; + auto *pData = propertyPrivate(self); + return pData != nullptr ? pData->tp_traverse(visit, arg) : 0; } static int qpropertyClear(PyObject *self) { - PySidePropertyPrivate *data = reinterpret_cast<PySideProperty *>(self)->d; - if (!data) + auto *data = reinterpret_cast<PySideProperty *>(self); + if (data->d == nullptr) return 0; - Py_CLEAR(data->fget); - Py_CLEAR(data->fset); - Py_CLEAR(data->freset); - Py_CLEAR(data->fdel); - Py_CLEAR(data->notify); - Py_CLEAR(data->pyTypeObject); - - delete data; - reinterpret_cast<PySideProperty *>(self)->d = nullptr; + auto *baseData = std::exchange(data->d, nullptr); + Q_ASSERT(baseData->type() == PySidePropertyBase::Type::Property); + static_cast<PySidePropertyPrivate *>(baseData)->tp_clear(); + delete baseData; return 0; } @@ -588,22 +632,22 @@ bool checkType(PyObject *pyObj) PyObject *getValue(PySideProperty *self, PyObject *source) { - return self->d->getValue(source); + return static_cast<PySidePropertyPrivate *>(self->d)->getValue(source); } int setValue(PySideProperty *self, PyObject *source, PyObject *value) { - return self->d->setValue(source, value); + return static_cast<PySidePropertyPrivate *>(self->d)->setValue(source, value); } int reset(PySideProperty *self, PyObject *source) { - return self->d->reset(source); + return static_cast<PySidePropertyPrivate *>(self->d)->reset(source); } const char *getTypeName(const PySideProperty *self) { - return self->d->typeName; + return self->d->typeName().constData(); } PySideProperty *getObject(PyObject *source, PyObject *name) @@ -624,28 +668,28 @@ PySideProperty *getObject(PyObject *source, PyObject *name) const char *getNotifyName(PySideProperty *self) { - if (self->d->notifySignature.isEmpty()) { - AutoDecRef str(PyObject_Str(self->d->notify)); - self->d->notifySignature = Shiboken::String::toCString(str); + if (self->d->notifySignature().isEmpty()) { + AutoDecRef str(PyObject_Str(self->d->notify())); + self->d->setNotifySignature(Shiboken::String::toCString(str)); } - return self->d->notifySignature.isEmpty() - ? nullptr : self->d->notifySignature.constData(); + return self->d->notifySignature().isEmpty() + ? nullptr : self->d->notifySignature().constData(); } void setTypeName(PySideProperty *self, const char *typeName) { - self->d->typeName = typeName; + self->d->setTypeName(typeName); } PyObject *getTypeObject(const PySideProperty *self) { - return self->d->pyTypeObject; + return self->d->pyTypeObject(); } PyObject *create(const char *typeName, PyObject *getter, PyObject *setter, PyObject *notifySignature, - PySidePropertyPrivate *data) + PySidePropertyBase *data) { Shiboken::AutoDecRef kwds(PyDict_New()); PyDict_SetItemString(kwds.object(), "type", PyUnicode_FromString(typeName)); @@ -669,7 +713,7 @@ PyObject *create(const char *typeName, PyObject *getter, PyObject *create(const char *typeName, PyObject *getter, PyObject *setter, const char *notifySignature, - PySidePropertyPrivate *data) + PySidePropertyBase *data) { PyObject *obNotifySignature = notifySignature != nullptr diff --git a/sources/pyside6/libpyside/pysideproperty.h b/sources/pyside6/libpyside/pysideproperty.h index 90c40e174..c2ce006a9 100644 --- a/sources/pyside6/libpyside/pysideproperty.h +++ b/sources/pyside6/libpyside/pysideproperty.h @@ -10,7 +10,7 @@ #include <QtCore/qmetaobject.h> -class PySidePropertyPrivate; +class PySidePropertyBase; extern "C" { @@ -19,7 +19,7 @@ extern "C" struct PYSIDE_API PySideProperty { PyObject_HEAD - PySidePropertyPrivate* d; + PySidePropertyBase* d; }; }; @@ -71,12 +71,12 @@ PYSIDE_API void setTypeName(PySideProperty *self, const char *typeName); /// Create a property from type, getter, setter and notification signature. PYSIDE_API PyObject *create(const char *typeName, PyObject *getter, PyObject *setter, PyObject *notifySignature, - PySidePropertyPrivate *data = nullptr); + PySidePropertyBase *data = nullptr); /// Create a property from type, getter, optional setter and notification signature. PYSIDE_API PyObject *create(const char *typeName, PyObject *getter, PyObject *setter = nullptr, const char *notifySignature = nullptr, - PySidePropertyPrivate *data = nullptr); + PySidePropertyBase *data = nullptr); } //namespace PySide::Property diff --git a/sources/pyside6/libpyside/pysideproperty_p.h b/sources/pyside6/libpyside/pysideproperty_p.h index bd223d0be..baf0df178 100644 --- a/sources/pyside6/libpyside/pysideproperty_p.h +++ b/sources/pyside6/libpyside/pysideproperty_p.h @@ -7,6 +7,7 @@ #include <sbkpython.h> #include "pysideproperty.h" +#include "pysidepropertybase_p.h" #include <pysidemacros.h> #include <QtCore/qbytearray.h> @@ -16,53 +17,34 @@ struct PySideProperty; -namespace PySide::Property { - -enum class PropertyFlag { - Readable = 0x001, - Writable = 0x002, - Resettable = 0x004, - Designable = 0x008, - Scriptable = 0x010, - Stored = 0x020, - User = 0x040, - Constant = 0x080, - Final = 0x100 -}; -Q_DECLARE_FLAGS(PropertyFlags, PropertyFlag) - -} // namespace PySide::Property - -class PYSIDE_API PySidePropertyPrivate +class PYSIDE_API PySidePropertyPrivate : public PySidePropertyBase { public: + PySidePropertyPrivate(const PySidePropertyPrivate &) = default; + PySidePropertyPrivate &operator=(const PySidePropertyPrivate &) = delete; + PySidePropertyPrivate(PySidePropertyPrivate &&) = delete; + PySidePropertyPrivate &operator=(PySidePropertyPrivate &&) = delete; - Q_DISABLE_COPY_MOVE(PySidePropertyPrivate) + PySidePropertyPrivate() : PySidePropertyBase(Type::Property) {} + ~PySidePropertyPrivate() override = default; - PySidePropertyPrivate() noexcept; - virtual ~PySidePropertyPrivate(); + [[nodiscard]] PySidePropertyPrivate *clone() const override; - virtual void metaCall(PyObject *source, QMetaObject::Call call, void **args); + void metaCall(PyObject *source, QMetaObject::Call call, void **args) override; + + void tp_clear(); + int tp_traverse(visitproc visit, void *arg); + void incref(); PyObject *getValue(PyObject *source) const; int setValue(PyObject *source, PyObject *value); int reset(PyObject *source); - static bool assignCheckCallable(PyObject *source, const char *name, PyObject **target); - - QByteArray typeName; - // Type object: A real PyTypeObject ("@Property(int)") or a string - // "@Property('QVariant')". - PyObject *pyTypeObject = nullptr; PyObject *fget = nullptr; PyObject *fset = nullptr; PyObject *freset = nullptr; PyObject *fdel = nullptr; - PyObject *notify = nullptr; bool getter_doc = false; - QByteArray notifySignature; - QByteArray doc; - PySide::Property::PropertyFlags flags; }; namespace PySide::Property { diff --git a/sources/pyside6/libpyside/pysidepropertybase_p.h b/sources/pyside6/libpyside/pysidepropertybase_p.h new file mode 100644 index 000000000..c8ef778ca --- /dev/null +++ b/sources/pyside6/libpyside/pysidepropertybase_p.h @@ -0,0 +1,95 @@ +// Copyright (C) 2025 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 + +#ifndef PYSIDE_PROPERTYBASE_P_H +#define PYSIDE_PROPERTYBASE_P_H + +#include <sbkpython.h> + +#include <pysidemacros.h> + +#include <QtCore/qbytearray.h> +#include <QtCore/qtclasshelpermacros.h> +#include <QtCore/qflags.h> +#include <QtCore/qmetaobject.h> + +struct PySideProperty; + +namespace PySide::Property { + +enum class PropertyFlag { + Readable = 0x001, + Writable = 0x002, + Resettable = 0x004, + Designable = 0x008, + Scriptable = 0x010, + Stored = 0x020, + User = 0x040, + Constant = 0x080, + Final = 0x100 +}; +Q_DECLARE_FLAGS(PropertyFlags, PropertyFlag) + +} // namespace PySide::Property + +// Base class for meta-callable properties (Normal properties, QmlListProperty) +class PYSIDE_API PySidePropertyBase +{ +public: + PySidePropertyBase &operator=(const PySidePropertyBase &) = delete; + PySidePropertyBase(PySidePropertyBase &&) = delete; + PySidePropertyBase &operator=(PySidePropertyBase &&) = delete; + + enum class Type : unsigned char { Property, ListProperty }; + + virtual ~PySidePropertyBase() = default; + + // For handling decorator like "@property.getter" + [[nodiscard]] virtual PySidePropertyBase *clone() const; + + virtual void metaCall(PyObject *source, QMetaObject::Call call, void **args) = 0; + + [[nodiscard]] Type type() const { return m_type; } + + [[nodiscard]] const QByteArray &typeName() const { return m_typeName; } + void setTypeName(const QByteArray &newTypeName) { m_typeName = newTypeName; } + + [[nodiscard]] PyObject *pyTypeObject() const { return m_pyTypeObject; } + void setPyTypeObject(PyObject *pt) { m_pyTypeObject = pt; } + + [[nodiscard]] PyObject *notify() const { return m_notify; } + void setNotify(PyObject *n) { m_notify = n; } + + [[nodiscard]] const QByteArray ¬ifySignature() const { return m_notifySignature; } + void setNotifySignature(const QByteArray &s) { m_notifySignature = s; } + + [[nodiscard]] const QByteArray &doc() const { return m_doc; } + void setDoc(const QByteArray &doc) { m_doc = doc; } + + [[nodiscard]] PySide::Property::PropertyFlags flags() const { return m_flags; } + void setFlags(PySide::Property::PropertyFlags f) { m_flags = f; } + void setFlag(PySide::Property::PropertyFlag f) { m_flags.setFlag(f); } + + static bool assignCheckCallable(PyObject *source, const char *name, PyObject **target); + +protected: + explicit PySidePropertyBase(Type t); + PySidePropertyBase(const PySidePropertyBase &rhs); + + void tp_clearBase(); + int tp_traverseBase(visitproc visit, void *arg); + void increfBase(); + +private: + QByteArray m_typeName; + // Type object: A real PyTypeObject ("@Property(int)") or a string + // "@Property('QVariant')". + PyObject *m_pyTypeObject = nullptr; + PyObject *m_notify = nullptr; + QByteArray m_notifySignature; + QByteArray m_doc; + PySide::Property::PropertyFlags m_flags; + Type m_type; +}; + +#endif // PYSIDE_PROPERTYBASE_P_H diff --git a/sources/pyside6/libpyside/signalmanager.cpp b/sources/pyside6/libpyside/signalmanager.cpp index 211588eea..01b08e283 100644 --- a/sources/pyside6/libpyside/signalmanager.cpp +++ b/sources/pyside6/libpyside/signalmanager.cpp @@ -404,7 +404,7 @@ int SignalManagerPrivate::qtPropertyMetacall(QObject *object, PyErr_WarnFormat(PyExc_RuntimeWarning, 0, ign ? "Unknown property type '%s' of QObject '%s' used in fset" : "Unknown property type '%s' of QObject '%s' used in fget with %R", - pp->d->typeName.constData(), metaObject->className(), errorStash.getException()); + pp->d->typeName().constData(), metaObject->className(), errorStash.getException()); if (PyErr_Occurred()) Shiboken::Errors::storeErrorOrPrint(); errorStash.release(); diff --git a/sources/pyside6/libpysideqml/pysideqmllistproperty.cpp b/sources/pyside6/libpysideqml/pysideqmllistproperty.cpp index de3e6b501..fd2014a35 100644 --- a/sources/pyside6/libpysideqml/pysideqmllistproperty.cpp +++ b/sources/pyside6/libpysideqml/pysideqmllistproperty.cpp @@ -15,16 +15,27 @@ #include <sbktypefactory.h> #include <pysideproperty.h> -#include <pysideproperty_p.h> +#include <pysidepropertybase_p.h> #include <pysideqobject.h> #include <QtCore/qobject.h> #include <QtQml/qqmllist.h> +#include <utility> + +using namespace Qt::StringLiterals; + // This is the user data we store in the property. -class QmlListPropertyPrivate : public PySidePropertyPrivate, public QmlListPropertyMixin +class QmlListPropertyPrivate : public PySidePropertyBase, public QmlListPropertyMixin { public: + QmlListPropertyPrivate(const QmlListPropertyPrivate &) = delete; + QmlListPropertyPrivate& operator=(const QmlListPropertyPrivate &) = delete; + QmlListPropertyPrivate(QmlListPropertyPrivate &&) = delete; + QmlListPropertyPrivate& operator=(QmlListPropertyPrivate &&) = delete; + + QmlListPropertyPrivate() : PySidePropertyBase(Type::ListProperty) {} + void metaCall(PyObject *source, QMetaObject::Call call, void **args) override { handleMetaCall(source, call, args); } @@ -36,7 +47,11 @@ public: void replace(QQmlListProperty<QObject> *propList, qsizetype index, QObject *value) override; void removeLast(QQmlListProperty<QObject> *propList) override; - PyTypeObject *elementType = nullptr; + void tp_clear(); + int tp_traverse(visitproc visit, void *arg); + void incref(); + + PyObject *obElementType = nullptr; PyObject *obAppend = nullptr; PyObject *obCount = nullptr; PyObject *obAt = nullptr; @@ -45,6 +60,50 @@ public: PyObject *obRemoveLast = nullptr; }; +void QmlListPropertyPrivate::tp_clear() +{ + PySidePropertyBase::tp_clearBase(); + Py_CLEAR(obElementType); + Py_CLEAR(obAppend); + Py_CLEAR(obCount); + Py_CLEAR(obAt); + Py_CLEAR(obClear); + Py_CLEAR(obReplace); + Py_CLEAR(obRemoveLast); +} + +int QmlListPropertyPrivate::tp_traverse(visitproc visit, void *arg) +{ + Py_VISIT(obElementType); + Py_VISIT(obAppend); + Py_VISIT(obCount); + Py_VISIT(obAt); + Py_VISIT(obClear); + Py_VISIT(obReplace); + Py_VISIT(obRemoveLast); + return PySidePropertyBase::tp_traverseBase(visit, arg); +} + +void QmlListPropertyPrivate::incref() +{ + PySidePropertyBase::increfBase(); + Py_XINCREF(obElementType); + Py_XINCREF(obAppend); + Py_XINCREF(obCount); + Py_XINCREF(obAt); + Py_XINCREF(obClear); + Py_XINCREF(obReplace); + Py_XINCREF(obRemoveLast); +} + +static inline QmlListPropertyPrivate *qmlListProperty(PyObject *self) +{ + auto *data = reinterpret_cast<PySideProperty *>(self); + Q_ASSERT(data->d != nullptr); + Q_ASSERT(data->d->type() == PySidePropertyBase::Type::ListProperty); + return static_cast<QmlListPropertyPrivate *>(data->d); +} + extern "C" { @@ -75,7 +134,7 @@ static int propListTpInit(PyObject *self, PyObject *args, PyObject *kwds) if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OOOOOOsObbbbbb:QtQml.ListProperty", const_cast<char **>(kwlist), - &data->elementType, + &data->obElementType, &append, &count, &at, &clear, &replace, &removeLast, /*s*/ &doc, /*O*/ ¬ify, // PySideProperty @@ -84,12 +143,12 @@ static int propListTpInit(PyObject *self, PyObject *args, PyObject *kwds) return -1; } - if (!PySidePropertyPrivate::assignCheckCallable(append, "append", &data->obAppend) - || !PySidePropertyPrivate::assignCheckCallable(count, "count", &data->obCount) - || !PySidePropertyPrivate::assignCheckCallable(at, "at", &data->obAt) - || !PySidePropertyPrivate::assignCheckCallable(clear, "clear", &data->obClear) - || !PySidePropertyPrivate::assignCheckCallable(replace, "replace", &data->obReplace) - || !PySidePropertyPrivate::assignCheckCallable(removeLast, "removeLast", &data->obRemoveLast)) { + if (!PySidePropertyBase::assignCheckCallable(append, "append", &data->obAppend) + || !PySidePropertyBase::assignCheckCallable(count, "count", &data->obCount) + || !PySidePropertyBase::assignCheckCallable(at, "at", &data->obAt) + || !PySidePropertyBase::assignCheckCallable(clear, "clear", &data->obClear) + || !PySidePropertyBase::assignCheckCallable(replace, "replace", &data->obReplace) + || !PySidePropertyBase::assignCheckCallable(removeLast, "removeLast", &data->obRemoveLast)) { return -1; } @@ -101,24 +160,22 @@ static int propListTpInit(PyObject *self, PyObject *args, PyObject *kwds) data->setMethodFlag(QmlListPropertyMixin::MethodFlag::RemoveLast, data->obRemoveLast != nullptr); if (notify != nullptr && notify != Py_None) - data->notify = notify; + data->setNotify(notify); - if (doc) - data->doc = doc; - else - data->doc.clear(); + data->setDoc(doc != nullptr ? QByteArray(doc) : QByteArray{}); PyTypeObject *qobjectType = PySide::qObjectType(); - if (!PySequence_Contains(data->elementType->tp_mro, reinterpret_cast<PyObject *>(qobjectType))) { + auto *elementType = reinterpret_cast<PyTypeObject *>(data->obElementType); + if (!PySequence_Contains(elementType->tp_mro, reinterpret_cast<PyObject *>(qobjectType))) { PyErr_Format(PyExc_TypeError, "A type inherited from %s expected, got %s.", - qobjectType->tp_name, data->elementType->tp_name); + qobjectType->tp_name, elementType->tp_name); return -1; } - data->typeName = QByteArrayLiteral("QQmlListProperty<QObject>"); + data->setTypeName("QQmlListProperty<QObject>"_ba); - auto &flags = data->flags; + PySide::Property::PropertyFlags flags; flags.setFlag(PySide::Property::PropertyFlag::Readable, true); flags.setFlag(PySide::Property::PropertyFlag::Designable, designable); flags.setFlag(PySide::Property::PropertyFlag::Scriptable, scriptable); @@ -126,15 +183,51 @@ static int propListTpInit(PyObject *self, PyObject *args, PyObject *kwds) flags.setFlag(PySide::Property::PropertyFlag::User, user); flags.setFlag(PySide::Property::PropertyFlag::Constant, constant); flags.setFlag(PySide::Property::PropertyFlag::Final, finalProp); + data->setFlags(flags); + + data->incref(); return 0; } +static int tp_propListTraverse(PyObject *self, visitproc visit, void *arg) +{ + auto *pData = qmlListProperty(self); + return pData != nullptr ? pData->tp_traverse(visit, arg) : 0; +} + +static int tp_propListClear(PyObject *self) +{ + auto *data = reinterpret_cast<PySideProperty *>(self); + if (data->d == nullptr) + return 0; + + auto *baseData = std::exchange(data->d, nullptr); + Q_ASSERT(baseData->type() == PySidePropertyBase::Type::ListProperty); + static_cast<QmlListPropertyPrivate *>(baseData)->tp_clear(); + delete baseData; + return 0; +} + +static void tp_propListDeAlloc(PyObject *self) +{ + tp_propListClear(self); + // PYSIDE-939: Handling references correctly. + // This was not needed before Python 3.8 (Python issue 35810) + Py_DECREF(Py_TYPE(self)); + PyObject_GC_UnTrack(self); + PepExt_TypeCallFree(self); +} + static PyTypeObject *createPropertyListType() { PyType_Slot PropertyListType_slots[] = { {Py_tp_new, reinterpret_cast<void *>(propList_tp_new)}, {Py_tp_init, reinterpret_cast<void *>(propListTpInit)}, + {Py_tp_dealloc, reinterpret_cast<void *>(tp_propListDeAlloc)}, + {Py_tp_traverse, reinterpret_cast<void *>(tp_propListTraverse)}, + {Py_tp_clear, reinterpret_cast<void *>(tp_propListClear)}, + {Py_tp_del, reinterpret_cast<void *>(PyObject_GC_Del)}, {0, nullptr} }; @@ -142,7 +235,7 @@ static PyTypeObject *createPropertyListType() "2:PySide6.QtQml.ListProperty", sizeof(PySideProperty), 0, - Py_TPFLAGS_DEFAULT, + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, PropertyListType_slots, }; @@ -219,6 +312,7 @@ QObject *QmlListPropertyPrivate::at(QQmlListProperty<QObject> *propList, qsizety Shiboken::AutoDecRef retVal(PyObject_CallObject(obAt, args)); QObject *result = nullptr; + auto *elementType = reinterpret_cast<PyTypeObject *>(obElementType); if (PyErr_Occurred()) PyErr_Print(); else if (PyType_IsSubtype(Py_TYPE(retVal), elementType)) diff --git a/sources/pyside6/tests/pysidetest/CMakeLists.txt b/sources/pyside6/tests/pysidetest/CMakeLists.txt index 4a7e2e1d1..8b4de5d8e 100644 --- a/sources/pyside6/tests/pysidetest/CMakeLists.txt +++ b/sources/pyside6/tests/pysidetest/CMakeLists.txt @@ -156,7 +156,6 @@ PYSIDE_TEST(new_inherited_functions_test.py) PYSIDE_TEST(notify_id.py) PYSIDE_TEST(properties_test.py) PYSIDE_TEST(property_python_test.py) -PYSIDE_TEST(snake_case_sub.py) PYSIDE_TEST(snake_case_test.py) PYSIDE_TEST(true_property_test.py) PYSIDE_TEST(qapp_like_a_macro_test.py) diff --git a/sources/pyside6/tests/pysidetest/snake_case_imported.py b/sources/pyside6/tests/pysidetest/snake_case_imported.py new file mode 100644 index 000000000..c79966e1e --- /dev/null +++ b/sources/pyside6/tests/pysidetest/snake_case_imported.py @@ -0,0 +1,25 @@ +# Copyright (C) 2025 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +from __future__ import annotations + +import os +import sys + +from pathlib import Path +sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) +from init_paths import init_test_paths # noqa: E402 +init_test_paths(False) + +from __feature__ import snake_case # noqa + +""" +PYSIDE-3250: Tests that snake_case works when used in several files +""" + +from PySide6.QtWidgets import QWidget # noqa: E402 + + +def test(): + print(__name__) + widget = QWidget() + return widget.size_hint() diff --git a/sources/pyside6/tests/pysidetest/snake_case_sub.py b/sources/pyside6/tests/pysidetest/snake_case_imported_no_snake_case.py index e423542a6..a5ce14694 100644 --- a/sources/pyside6/tests/pysidetest/snake_case_sub.py +++ b/sources/pyside6/tests/pysidetest/snake_case_imported_no_snake_case.py @@ -20,4 +20,4 @@ from PySide6.QtWidgets import QWidget # noqa: E402 def test_no_snake_case(): print(__name__) widget = QWidget() - check = widget.sizeHint # noqa + return widget.sizeHint() diff --git a/sources/pyside6/tests/pysidetest/snake_case_test.py b/sources/pyside6/tests/pysidetest/snake_case_test.py index bdcd996c4..4667e584a 100644 --- a/sources/pyside6/tests/pysidetest/snake_case_test.py +++ b/sources/pyside6/tests/pysidetest/snake_case_test.py @@ -21,18 +21,26 @@ if not is_pypy: from __feature__ import snake_case # noqa from helper.usesqapplication import UsesQApplication -import snake_case_sub +import snake_case_imported +import snake_case_imported_no_snake_case @unittest.skipIf(is_pypy, "__feature__ cannot yet be used with PyPy") class SnakeCaseNoPropagateTest(UsesQApplication): - def testSnakeCase(self): - # this worked + def testSnakeCaseImport(self): + """PYSIDE-3250: Test that snake case works when using it in imported modules.""" widget = QWidget() - check = widget.size_hint # noqa + r1 = widget.size_hint() + r2 = snake_case_imported.test() + self.assertEqual(r1, r2) - snake_case_sub.test_no_snake_case() + def testSnakeCaseImportNoSnakeCase(self): + """PYSIDE-2029: Tests that snake_case is isolated from imported modules.""" + widget = QWidget() + r1 = widget.size_hint() + r2 = snake_case_imported_no_snake_case.test_no_snake_case() + self.assertEqual(r1, r2) if __name__ == '__main__': diff --git a/sources/shiboken6/libshiboken/signature/signature.cpp b/sources/shiboken6/libshiboken/signature/signature.cpp index 9a8d5080a..85cc60e30 100644 --- a/sources/shiboken6/libshiboken/signature/signature.cpp +++ b/sources/shiboken6/libshiboken/signature/signature.cpp @@ -310,27 +310,30 @@ static PyObject *feature_import(PyObject * /* self */, PyObject *args, PyObject if (origImportFunc == nullptr) { Py_FatalError("libshiboken: builtins has no \"__orig_import__\" function"); } - // PYSIDE-3054: Instead of just calling the original import, we temporarily - // reset the whole import function to the previous version. - // This prevents unforeseen recursions like in settrace. - PyObject *featureImportFunc = PyDict_GetItemString(builtins.object(), "__import__"); - Py_INCREF(origImportFunc); - Py_INCREF(featureImportFunc); - PyDict_SetItemString(builtins.object(), "__import__", origImportFunc); ret = PyObject_Call(origImportFunc, args, kwds); if (ret) { + // PYSIDE-3054: Instead of just calling the original import, we temporarily + // reset the whole import function to the previous version. + // This prevents unforeseen recursions like in settrace. + PyObject *featureImportFunc = PyDict_GetItemString(builtins.object(), "__import__"); + Py_INCREF(origImportFunc); + Py_INCREF(featureImportFunc); + PyDict_SetItemString(builtins.object(), "__import__", origImportFunc); + // PYSIDE-2029: Intercept after the import to search for PySide usage. PyObject *post = PyObject_CallFunctionObjArgs(pyside_globals->feature_imported_func, ret, nullptr); Py_XDECREF(post); + + PyDict_SetItemString(builtins.object(), "__import__", featureImportFunc); + Py_DECREF(origImportFunc); + Py_DECREF(featureImportFunc); + if (post == nullptr) { Py_DECREF(ret); ret = nullptr; } } - PyDict_SetItemString(builtins.object(), "__import__", featureImportFunc); - Py_DECREF(origImportFunc); - Py_DECREF(featureImportFunc); return ret; } diff --git a/sources/shiboken6/tests/libsample/spaceship.cpp b/sources/shiboken6/tests/libsample/spaceship.cpp index b30f2f30f..c883f7c2f 100644 --- a/sources/shiboken6/tests/libsample/spaceship.cpp +++ b/sources/shiboken6/tests/libsample/spaceship.cpp @@ -8,6 +8,13 @@ SpaceshipComparisonTester::SpaceshipComparisonTester(int v) noexcept { } +#if __cplusplus >= 202002 || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002) +std::strong_ordering SpaceshipComparisonTester::operator<=>(int rhs) const +{ + return m_value <=> rhs; +} +#endif // C++ 20 + FreeSpaceshipComparisonTester::FreeSpaceshipComparisonTester(int v) noexcept : m_value(v) { @@ -25,3 +32,18 @@ std::strong_ordering operator<=>(FreeSpaceshipComparisonTester lhs, return lhs.value() <=> rhs.value(); } #endif // C++ 20 + +NonEqualityComparisonTester::NonEqualityComparisonTester(int v) noexcept + : m_value(v) +{ +} + +int NonEqualityComparisonTester::value() const +{ + return m_value; +} + +bool NonEqualityComparisonTester::operator==(NonEqualityComparisonTester rhs) const +{ + return m_value == rhs.m_value; +} diff --git a/sources/shiboken6/tests/libsample/spaceship.h b/sources/shiboken6/tests/libsample/spaceship.h index 0d0854fe6..26f636009 100644 --- a/sources/shiboken6/tests/libsample/spaceship.h +++ b/sources/shiboken6/tests/libsample/spaceship.h @@ -17,6 +17,7 @@ public: #if __cplusplus >= 202002 || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002) auto operator<=>(const SpaceshipComparisonTester &rhs) const = default; + std::strong_ordering operator<=>(int rhs) const; enum Enabled { HasSpaceshipOperator = 1 }; #else @@ -44,4 +45,18 @@ LIBSAMPLE_API std::strong_ordering operator<=>(FreeSpaceshipComparisonTester lhs FreeSpaceshipComparisonTester rhs); #endif // C++ 20 + +class LIBSAMPLE_API NonEqualityComparisonTester +{ +public: + explicit NonEqualityComparisonTester(int v) noexcept; + + int value() const; + + bool operator==(NonEqualityComparisonTester rhs) const; + +private: + int m_value; +}; + #endif // SPACESHIP_H diff --git a/sources/shiboken6/tests/samplebinding/CMakeLists.txt b/sources/shiboken6/tests/samplebinding/CMakeLists.txt index c9e4f601f..01f51fc2d 100644 --- a/sources/shiboken6/tests/samplebinding/CMakeLists.txt +++ b/sources/shiboken6/tests/samplebinding/CMakeLists.txt @@ -64,6 +64,7 @@ ${CMAKE_CURRENT_BINARY_DIR}/sample/moveonly_wrapper.cpp ${CMAKE_CURRENT_BINARY_DIR}/sample/moveonlyhandler_wrapper.cpp ${CMAKE_CURRENT_BINARY_DIR}/sample/noimplicitconversion_wrapper.cpp ${CMAKE_CURRENT_BINARY_DIR}/sample/nondefaultctor_wrapper.cpp +${CMAKE_CURRENT_BINARY_DIR}/sample/nonequalitycomparisontester_wrapper.cpp ${CMAKE_CURRENT_BINARY_DIR}/sample/objectmodel_wrapper.cpp ${CMAKE_CURRENT_BINARY_DIR}/sample/objecttype_wrapper.cpp ${CMAKE_CURRENT_BINARY_DIR}/sample/objecttypebyvalue_wrapper.cpp diff --git a/sources/shiboken6/tests/samplebinding/spaceship_test.py b/sources/shiboken6/tests/samplebinding/spaceship_test.py index 92d65d4ee..95d2506ce 100644 --- a/sources/shiboken6/tests/samplebinding/spaceship_test.py +++ b/sources/shiboken6/tests/samplebinding/spaceship_test.py @@ -14,7 +14,8 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from shiboken_paths import init_paths init_paths() -from sample import FreeSpaceshipComparisonTester, SpaceshipComparisonTester +from sample import (FreeSpaceshipComparisonTester, SpaceshipComparisonTester, + NonEqualityComparisonTester) class SpaceshipTest(unittest.TestCase): @@ -31,6 +32,12 @@ class SpaceshipTest(unittest.TestCase): self.assertFalse(t1 > t2) @unittest.skipUnless(SpaceshipComparisonTester.Enabled.HasSpaceshipOperator, "< C++ 20") + def testNonHomogeneousSpaceshipOperator(self): + t = SpaceshipComparisonTester(42) + self.assertTrue(t < 43) + self.assertTrue(t > 41) + + @unittest.skipUnless(SpaceshipComparisonTester.Enabled.HasSpaceshipOperator, "< C++ 20") def testFreeSpaceshipOperator(self): """Test a free operator<=>(). It does not provide equality as it is not defaulted.""" @@ -39,6 +46,15 @@ class SpaceshipTest(unittest.TestCase): self.assertTrue(t1 < t2) self.assertFalse(t1 > t2) + @unittest.skipUnless(SpaceshipComparisonTester.Enabled.HasSpaceshipOperator, "< C++ 20") + def testNonEqualSynthetization(self): + ne_a = NonEqualityComparisonTester(1) + ne_b = NonEqualityComparisonTester(1) + self.assertTrue(ne_a == ne_b) + # Verify that different instances with same value are not reported as "not equal", + # (fooling the FallbackRichCompare() function which is generated for missing operators). + self.assertFalse(ne_a != ne_b) + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken6/tests/samplebinding/typesystem_sample.xml b/sources/shiboken6/tests/samplebinding/typesystem_sample.xml index fed84ba0d..05798a6ce 100644 --- a/sources/shiboken6/tests/samplebinding/typesystem_sample.xml +++ b/sources/shiboken6/tests/samplebinding/typesystem_sample.xml @@ -160,6 +160,7 @@ <enum-type name="Enabled"/> </value-type> <value-type name="FreeSpaceshipComparisonTester"/> + <value-type name="NonEqualityComparisonTester"/> <primitive-type name="PStr"> <include file-name="str.h" location="global"/> diff --git a/sources/shiboken6_generator/ApiExtractor/abstractmetabuilder.cpp b/sources/shiboken6_generator/ApiExtractor/abstractmetabuilder.cpp index 2413cc1ad..bf8d3246c 100644 --- a/sources/shiboken6_generator/ApiExtractor/abstractmetabuilder.cpp +++ b/sources/shiboken6_generator/ApiExtractor/abstractmetabuilder.cpp @@ -278,8 +278,35 @@ void AbstractMetaBuilderPrivate::registerToStringCapability(const FunctionModelI } } +// Find "operator!=" matching an "operator==" in a scope. +static bool hasOperatorNotEqual(const ScopeModelItem &scopeItem, const FunctionModelItem &operatorEqual) +{ + auto pred = [&operatorEqual](const FunctionModelItem &f) { + return f->isOperatorNotEqual() && operatorEqual->hasEquivalentArguments(*f); + }; + return std::any_of(scopeItem->functions().cbegin(), scopeItem->functions().cend(), pred); +} + +static ComparisonOperators synthesizedSpaceshipComparison(const AbstractMetaClassCPtr ¤tClass, + const FunctionModelItem &item) +{ + const auto te = currentClass->typeEntry(); + // operator "<", ">" not for non-pair type containers + if (te->isContainer()) { + auto cTe = std::static_pointer_cast<const ContainerTypeEntry>(te); + if (cTe->containerKind() != ContainerTypeEntry::PairContainer) + return ComparisonOperatorType::EqualityMask; + } + + // An == operator function is declared implicitly for each operator<=> + // defined as defaulted. + return item->attributes().testFlag(FunctionAttribute::Defaulted) + ? ComparisonOperatorType::AllMask : ComparisonOperatorType::OrderingMask; +} + // Traverse free operator functions (global/namespace) void AbstractMetaBuilderPrivate::traverseFreeOperatorFunction(const FunctionModelItem &item, + const ScopeModelItem &scope, const AbstractMetaClassPtr ¤tClass) { Q_ASSERT(!currentClass || currentClass->isNamespace()); @@ -315,12 +342,6 @@ void AbstractMetaBuilderPrivate::traverseFreeOperatorFunction(const FunctionMode return; } - if (item->isSpaceshipOperator() && !item->isDeleted()) { - AbstractMetaClass::addSynthesizedComparisonOperators(baseoperandClass, - InternalFunctionFlag::OperatorCpp20Spaceship); - return; - } - // Do not synthesize reverse comparison operators. CPython swaps the // arguments for them by itself in Py_tp_richcompare. const bool reverseOperator = !firstArgumentIsSelf && !unaryOperator; @@ -359,6 +380,27 @@ void AbstractMetaBuilderPrivate::traverseFreeOperatorFunction(const FunctionMode if (metaFunction->isComparisonOperator()) metaFunction->setConstant(true); metaFunction->setAccess(Access::Public); + if (item->isSpaceshipOperator()) { + // For spaceship, the traverse mechanism is only used to handle rejections + // and get the argument type. + const auto ops = synthesizedSpaceshipComparison(baseoperandClass, item); + flags.setFlag(InternalFunctionFlag::OperatorCpp20Spaceship); + AbstractMetaClass::addSynthesizedComparisonOperators(baseoperandClass, + metaFunction->arguments(), + ops, flags); + return; + } + + // C++20: Synthesize "!=" from "==" + if (clang::emulatedCompilerLanguageLevel() >= LanguageLevel::Cpp20 + && item->isOperatorEqual() + && !item->hasPointerArguments() && !hasOperatorNotEqual(scope, item)) { + AbstractMetaClass::addSynthesizedComparisonOperators( + baseoperandClass, metaFunction->arguments(), + ComparisonOperatorType::OperatorNotEqual, + flags | InternalFunctionFlag::OperatorCpp20NonEquality); + } + AbstractMetaClass::addFunction(baseoperandClass, metaFunction); ReportHandler::addGeneralMessage(msgSynthesizedFunction(metaFunction, item)); if (!metaFunction->arguments().isEmpty()) { @@ -676,11 +718,11 @@ void AbstractMetaBuilderPrivate::traverseDom(const FileModelItem &dom, case CodeModel::ArithmeticOperator: case CodeModel::BitwiseOperator: case CodeModel::LogicalOperator: - traverseFreeOperatorFunction(func, {}); + traverseFreeOperatorFunction(func, dom, {}); break; case CodeModel::ShiftOperator: if (!traverseStreamOperator(func, {})) - traverseFreeOperatorFunction(func, {}); + traverseFreeOperatorFunction(func, dom, {}); default: break; } @@ -1465,7 +1507,7 @@ void AbstractMetaBuilderPrivate::traverseNameSpaceFunctions(const ScopeModelItem functions.reserve(scopeFunctionList.size()); for (const FunctionModelItem &function : scopeFunctionList) { if (function->isOperator()) { - traverseFreeOperatorFunction(function, currentClass); + traverseFreeOperatorFunction(function, scopeItem, currentClass); } else if (auto metaFunction = traverseFunction(function, currentClass)) { metaFunction->setCppAttribute(FunctionAttribute::Static); functions.append(metaFunction); @@ -1548,8 +1590,27 @@ void AbstractMetaBuilderPrivate::traverseClassFunction(const ScopeModelItem& sco const AbstractMetaFunctionPtr &metaFunction, const AbstractMetaClassPtr &metaClass) const { - Q_UNUSED(scopeItem) - Q_UNUSED(function) + if (function->isSpaceshipOperator()) { + // For spaceship, the traverse mechanism is only used to handle rejections + // and get the argument type. + if (!function->isDeleted()) { + const auto ops = synthesizedSpaceshipComparison(metaClass, function); + AbstractMetaClass::addSynthesizedComparisonOperators(metaClass, + metaFunction->arguments(), + ops, InternalFunctionFlag::OperatorCpp20Spaceship); + } + return; + } + + // C++20: Synthesize "!=" from "==" + if (clang::emulatedCompilerLanguageLevel() >= LanguageLevel::Cpp20 + && function->isOperatorEqual() && !hasOperatorNotEqual(scopeItem, function)) { + AbstractMetaClass::addSynthesizedComparisonOperators( + metaClass, metaFunction->arguments(), + ComparisonOperatorType::OperatorNotEqual, + InternalFunctionFlag::OperatorCpp20NonEquality); + } + traverseClassFunction(metaFunction, metaClass); } @@ -1559,10 +1620,7 @@ void AbstractMetaBuilderPrivate::traverseClassFunctions(const ScopeModelItem& sc Q_ASSERT(metaClass); AbstractMetaClass::Attributes constructorAttributes; for (const FunctionModelItem &function : scopeItem->functions()) { - if (function->isSpaceshipOperator() && !function->isDeleted()) { - AbstractMetaClass::addSynthesizedComparisonOperators(metaClass, - InternalFunctionFlag::OperatorCpp20Spaceship); - } else if (auto metaFunction = traverseFunction(function, metaClass)) { + if (auto metaFunction = traverseFunction(function, metaClass)) { traverseClassFunction(scopeItem, function, metaFunction, metaClass); } else if (!function->isDeleted() && function->functionType() == CodeModel::Constructor) { // traverseFunction() failed: mark rejected constructors diff --git a/sources/shiboken6_generator/ApiExtractor/abstractmetabuilder_p.h b/sources/shiboken6_generator/ApiExtractor/abstractmetabuilder_p.h index b503f4b33..0890eb752 100644 --- a/sources/shiboken6_generator/ApiExtractor/abstractmetabuilder_p.h +++ b/sources/shiboken6_generator/ApiExtractor/abstractmetabuilder_p.h @@ -116,7 +116,7 @@ public: void traverseFields(const ScopeModelItem &item, const AbstractMetaClassPtr &parent); bool traverseStreamOperator(const FunctionModelItem &functionItem, const AbstractMetaClassPtr ¤tClass); - void traverseFreeOperatorFunction(const FunctionModelItem &item, + void traverseFreeOperatorFunction(const FunctionModelItem &item, const ScopeModelItem &scope, const AbstractMetaClassPtr ¤tClass); AbstractMetaFunctionPtr traverseAddedFunctionHelper(const AddedFunctionPtr &addedFunc, diff --git a/sources/shiboken6_generator/ApiExtractor/abstractmetalang.cpp b/sources/shiboken6_generator/ApiExtractor/abstractmetalang.cpp index 837ce0d1a..14ed79644 100644 --- a/sources/shiboken6_generator/ApiExtractor/abstractmetalang.cpp +++ b/sources/shiboken6_generator/ApiExtractor/abstractmetalang.cpp @@ -920,35 +920,30 @@ static AbstractMetaType boolType() return result; } -// Helper to synthesize comparison operators from a spaceship operator. Since -// shiboken also generates code for comparing to different types, this fits -// better than of handling it in the generator code. +// Helper to synthesize comparison operators from a spaceship operator and equality operators. +// Since shiboken also generates code for comparing to different types, this fits +// better than handling it in the generator code. void AbstractMetaClass::addSynthesizedComparisonOperators(const AbstractMetaClassPtr &c, + const AbstractMetaArgumentList &arguments, + ComparisonOperators ops, InternalFunctionFlags flags) { static const auto returnType = boolType(); - AbstractMetaType selfType(c->typeEntry()); - selfType.setConstant(true); - selfType.setReferenceType(LValueReference); - selfType.decideUsagePattern(); - AbstractMetaArgument selfArgument; - selfArgument.setType(selfType); - selfArgument.setName(u"rhs"_s); - AbstractMetaArgumentList arguments(1, selfArgument); - - static const char *operators[] - = {"operator==", "operator!=", "operator<", "operator<=", "operator>", "operator>="}; - for (const auto *op : operators) { - auto *f = AbstractMetaClassPrivate::createFunction(QLatin1StringView(op), - AbstractMetaFunction::ComparisonOperator, - Access::Public, arguments, - returnType, c); - f->setFlags(f->flags() | flags); - f->setConstant(true); - AbstractMetaFunctionCPtr newFunction(f); - c->d->addFunction(newFunction); - ReportHandler::addGeneralMessage(msgSynthesizedFunction(newFunction)); + for (int mask = 0x1; (mask & int(ComparisonOperatorType::AllMask)) != 0; mask <<= 1) { + const auto op = ComparisonOperatorType(mask); + if (ops.testFlag(op)) { + const QString name = "operator"_L1 + QLatin1StringView(AbstractMetaFunction::cppComparisonOperator(op)); + auto *f = AbstractMetaClassPrivate::createFunction(name, + AbstractMetaFunction::ComparisonOperator, + Access::Public, arguments, + returnType, c); + f->setFlags(f->flags() | flags); + f->setConstant(true); + AbstractMetaFunctionCPtr newFunction(f); + c->d->addFunction(newFunction); + ReportHandler::addGeneralMessage(msgSynthesizedFunction(newFunction)); + } } } diff --git a/sources/shiboken6_generator/ApiExtractor/abstractmetalang.h b/sources/shiboken6_generator/ApiExtractor/abstractmetalang.h index 8cc2f71f8..fd9b89443 100644 --- a/sources/shiboken6_generator/ApiExtractor/abstractmetalang.h +++ b/sources/shiboken6_generator/ApiExtractor/abstractmetalang.h @@ -128,6 +128,8 @@ public: bool isCopyConstructible() const; static void addSynthesizedComparisonOperators(const AbstractMetaClassPtr &c, + const AbstractMetaArgumentList &arguments, + ComparisonOperators ops, InternalFunctionFlags flags); bool generateExceptionHandling() const; diff --git a/sources/shiboken6_generator/ApiExtractor/clangparser/clangbuilder.cpp b/sources/shiboken6_generator/ApiExtractor/clangparser/clangbuilder.cpp index 407a7a9e7..8e262d6d8 100644 --- a/sources/shiboken6_generator/ApiExtractor/clangparser/clangbuilder.cpp +++ b/sources/shiboken6_generator/ApiExtractor/clangparser/clangbuilder.cpp @@ -397,6 +397,7 @@ FunctionModelItem BuilderPrivate::createMemberFunction(const CXCursor &cursor, result->setAttribute(FunctionAttribute::Static, clang_CXXMethod_isStatic(cursor) != 0); result->setAttribute(FunctionAttribute::Virtual, clang_CXXMethod_isVirtual(cursor) != 0); result->setAttribute(FunctionAttribute::Abstract, clang_CXXMethod_isPureVirtual(cursor) != 0); + result->setAttribute(FunctionAttribute::Defaulted, clang_CXXMethod_isDefaulted(cursor) != 0); return result; } diff --git a/sources/shiboken6_generator/ApiExtractor/parser/codemodel.cpp b/sources/shiboken6_generator/ApiExtractor/parser/codemodel.cpp index 8cbca7bc9..bfccacbac 100644 --- a/sources/shiboken6_generator/ApiExtractor/parser/codemodel.cpp +++ b/sources/shiboken6_generator/ApiExtractor/parser/codemodel.cpp @@ -846,6 +846,13 @@ void _ArgumentModelItem::setScopeResolution(bool v) m_scopeResolution = v; } +bool _ArgumentModelItem::isEquivalent(const _ArgumentModelItem &rhs) const +{ + return m_scopeResolution == rhs.m_scopeResolution && m_defaultValue == rhs.m_defaultValue + && m_defaultValueExpression == rhs.m_defaultValueExpression + && m_type == rhs.m_type; +} + #ifndef QT_NO_DEBUG_STREAM void _ArgumentModelItem::formatDebug(QDebug &d) const { @@ -991,6 +998,16 @@ bool _FunctionModelItem::isOperator() const return result; } +static bool isPointerArgument(const ArgumentModelItem &a) +{ + return !a->type().indirectionsV().isEmpty(); +} + +bool _FunctionModelItem::hasPointerArguments() const +{ + return std::any_of(m_arguments.cbegin(), m_arguments.cend(), isPointerArgument); +} + ExceptionSpecification _FunctionModelItem::exceptionSpecification() const { return m_exceptionSpecification; @@ -1075,6 +1092,19 @@ QString _FunctionModelItem::typeSystemSignature() const // For dumping out type return result; } +static inline bool equivalentArguments(const ArgumentModelItem &lhs, + const ArgumentModelItem &rhs) +{ + return lhs->isEquivalent(*rhs); +} + +bool _FunctionModelItem::hasEquivalentArguments(const _FunctionModelItem &rhs) const +{ + return m_arguments.size() == rhs.m_arguments.size() + && std::equal(m_arguments.cbegin(), m_arguments.cend(), rhs.m_arguments.cbegin(), rhs.m_arguments.cend(), + equivalentArguments); +} + using NameFunctionTypeHash = QHash<QStringView, CodeModel::FunctionType>; static const NameFunctionTypeHash &nameToOperatorFunction() diff --git a/sources/shiboken6_generator/ApiExtractor/parser/codemodel.h b/sources/shiboken6_generator/ApiExtractor/parser/codemodel.h index 8d757e635..7ff0a88e2 100644 --- a/sources/shiboken6_generator/ApiExtractor/parser/codemodel.h +++ b/sources/shiboken6_generator/ApiExtractor/parser/codemodel.h @@ -380,6 +380,8 @@ public: bool scopeResolution() const; void setScopeResolution(bool v); + bool isEquivalent(const _ArgumentModelItem &rhs) const; // Compare all except name + #ifndef QT_NO_DEBUG_STREAM void formatDebug(QDebug &d) const override; #endif @@ -502,6 +504,7 @@ public: bool isSpaceshipOperator() const; bool isOperatorEqual() const; bool isOperatorNotEqual() const; + bool hasPointerArguments() const; bool isSimilar(const FunctionModelItem &other) const; @@ -515,6 +518,9 @@ public: QString classQualifiedSignature() const; QString typeSystemSignature() const; // For dumping out type system files + // Compare all except names + bool hasEquivalentArguments(const _FunctionModelItem &rhs) const; + // Private, for usage by the clang builder. void _determineType(); diff --git a/sources/shiboken6_generator/ApiExtractor/parser/codemodel_enums.h b/sources/shiboken6_generator/ApiExtractor/parser/codemodel_enums.h index 358195799..272140ae3 100644 --- a/sources/shiboken6_generator/ApiExtractor/parser/codemodel_enums.h +++ b/sources/shiboken6_generator/ApiExtractor/parser/codemodel_enums.h @@ -53,6 +53,7 @@ enum class FunctionAttribute : std::uint8_t { Final = 0x00000010, Deprecated = 0x00000020, // Code annotation Explicit = 0x00000040, // Constructor + Defaulted = 0x00000080 }; Q_DECLARE_FLAGS(FunctionAttributes, FunctionAttribute) diff --git a/sources/shiboken6_generator/ApiExtractor/typedatabase.cpp b/sources/shiboken6_generator/ApiExtractor/typedatabase.cpp index 91d39f835..ae06fb140 100644 --- a/sources/shiboken6_generator/ApiExtractor/typedatabase.cpp +++ b/sources/shiboken6_generator/ApiExtractor/typedatabase.cpp @@ -1739,6 +1739,17 @@ void TypeDatabasePrivate::addBuiltInPrimitiveTypes() root, rootPackage, pyUnicodeCustomEntry); } + + // Prevent rejection of operator<=>() due to mismatched return type. + if (clang::emulatedCompilerLanguageLevel() >= LanguageLevel::Cpp20) { + for (const QString &ordering : {u"std::strong_ordering"_s, u"std::partial_ordering"_s}) { + if (!m_entries.contains(ordering)) { + auto entry = std::make_shared<CustomTypeEntry>(ordering, QVersionNumber{}, root); + entry->setTargetLangPackage(rootPackage); + m_entries.insert(ordering, entry); + } + } + } } QDebug operator<<(QDebug d, const TypeDatabase &db) |
