// Copyright (C) 2025 Ford Motor Company // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "pysidedynamicpod_p.h" #include "pysidecapsulemethod_p.h" #include "pysidedynamiccommon_p.h" #include #include #include #include #include #include #include #include using namespace Shiboken; extern "C" { struct PodDefs { static PyObject *tp_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { SBK_UNUSED(kwds); AutoDecRef param_types(PyObject_GetAttrString(reinterpret_cast(type), "__param_types__")); if (!param_types) { PyErr_Format(PyExc_RuntimeError, "Failed to get POD attributes for type %s", type->tp_name); return nullptr; } // param_types is a tuple of PyTypeObject pointers Py_ssize_t size = PyTuple_Size(param_types); if (size != PyTuple_Size(args)) { PyErr_Format(PyExc_TypeError, "Incorrect number of arguments for type %s. Expected %zd.", type->tp_name, size); return nullptr; } PyObject *self = PepExt_Type_GetAllocSlot(type)(type, size); if (!self) return nullptr; for (Py_ssize_t i = 0; i < size; ++i) { PyObject *expected_type = PyTuple_GetItem(param_types, i); PyObject *item = PyTuple_GetItem(args, i); // Check if the item is an instance of the expected type if (PyObject_IsInstance(item, expected_type)) { Py_INCREF(item); PyTuple_SetItem(self, i, item); } else { // Try to convert the item to the expected type PyObject *converted_item = PyObject_CallFunctionObjArgs(expected_type, item, nullptr); if (!converted_item) { Py_DECREF(self); PyErr_Format(PyExc_TypeError, "Argument %zd must be convertible to type %s", i, reinterpret_cast(expected_type)->tp_name); return nullptr; } PyTuple_SetItem(self, i, converted_item); } } return self; } static PyObject *tp_repr(PyObject *self) { auto *type = Py_TYPE(self); std::string repr(type->tp_name); repr += "("; for (Py_ssize_t i = 0; i < PyTuple_Size(self); ++i) { if (i > 0) repr += ", "; PyObject *item_repr = PyObject_Repr(PyTuple_GetItem(self, i)); repr += String::toCString(item_repr); } repr += ")"; return PyUnicode_FromString(repr.c_str()); } static PyObject *CapsuleMethod_handler(PyObject *payload, PyObject * /* args */) { auto *methodData = reinterpret_cast( PyCapsule_GetPointer(payload, "Payload")); if (!methodData) { PyErr_SetString(PyExc_RuntimeError, "Invalid call to dynamic method. Missing payload."); return nullptr; } PyObject *self = methodData->self; if (PyCapsule_IsValid(methodData->payload, "PropertyCapsule")) { // Handle property getter/setter against our hidden properties attribute auto *capsule = PyCapsule_GetPointer(methodData->payload, "PropertyCapsule"); if (capsule) { auto *callData = reinterpret_cast(capsule); if (callData->indexInObject < 0 || callData->indexInObject >= PyTuple_Size(self)) { PyErr_Format(PyExc_RuntimeError, "Unknown property method: %s", callData->name.constData()); return nullptr; } auto *val = PyTuple_GetItem(self, callData->indexInObject); Py_INCREF(val); return val; } } PyErr_SetString(PyExc_RuntimeError, "Unknown capsule type"); return nullptr; } }; static PyMethodDef DynamicPod_tp_methods[] = { {"get_enum", reinterpret_cast(DynamicType_get_enum), METH_O | METH_CLASS, "Get enum type by name"}, {nullptr, nullptr, 0, nullptr} }; static PyType_Slot DynamicPod_slots[] = { {Py_tp_base, reinterpret_cast(&PyTuple_Type)}, {Py_tp_new, reinterpret_cast(PodDefs::tp_new)}, {Py_tp_repr, reinterpret_cast(PodDefs::tp_repr)}, {Py_tp_methods, reinterpret_cast(DynamicPod_tp_methods)}, {0, nullptr} }; // C++ to Python conversion for POD types. static PyObject *cppToPython_POD_Tuple(const void *cppIn) { const auto &cppInRef = *reinterpret_cast(cppIn); PyObject *pyOut = PyTuple_New(Py_ssize_t(cppInRef.size())); Py_ssize_t idx = 0; for (auto it = std::cbegin(cppInRef), end = std::cend(cppInRef); it != end; ++it, ++idx) { static const Conversions::SpecificConverter argConverter("QVariant"); const auto &cppItem = *it; PyTuple_SetItem(pyOut, idx, Shiboken::Conversions::copyToPython(argConverter, &cppItem)); } return pyOut; } static void pythonToCpp_Tuple_POD(PyObject *pyIn, void *cppOut) { auto &cppOutRef = *reinterpret_cast(cppOut); Py_ssize_t tupleSize = PyTuple_Size(pyIn); if (tupleSize != cppOutRef.size()) { PyErr_Format(PyExc_ValueError, "Size mismatch: tuple has %zd elements, but POD expects %d elements", tupleSize, cppOutRef.size()); return; } for (Py_ssize_t i = 0; i < tupleSize; ++i) { static const Conversions::SpecificConverter argConverter("QVariant"); PyObject *item = PyTuple_GetItem(pyIn, i); QVariant &variant = cppOutRef[i]; Conversions::SpecificConverter converter(variant.metaType().name()); Shiboken::Conversions::pythonToCppCopy(converter, item, variant.data()); } } static PythonToCppFunc is_Tuple_PythonToCpp_POD_Convertible(PyObject *pyIn) { if (PyTuple_Check(pyIn)) return pythonToCpp_Tuple_POD; return {}; } } // extern "C" PyTypeObject *createPodType(QMetaObject *meta) { auto qualname = QByteArrayLiteral("DynamicPod.") + meta->className(); PyType_Spec spec = { qualname.constData(), 0, 0, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_TYPE_SUBCLASS, DynamicPod_slots }; PyObject *obType = PyType_FromSpec(&spec); if (!obType) return nullptr; if (create_managed_py_enums(obType, meta) < 0) return nullptr; Py_ssize_t size = meta->propertyCount() - meta->propertyOffset(); AutoDecRef pyParamTypes(PyTuple_New(size)); for (int i = 0; i < size; ++i) { auto metaProperty = meta->property(i + meta->propertyOffset()); auto metaType = metaProperty.metaType(); if (!metaType.isValid()) { PyErr_Format(PyExc_RuntimeError, "Failed to get meta type for property %s", metaProperty.name()); return nullptr; } auto *pyType = Conversions::getPythonTypeObject(metaType.name()); Py_INCREF(pyType); PyTuple_SetItem(pyParamTypes, i, reinterpret_cast(pyType)); } auto *type = reinterpret_cast(obType); PyMethodDef method = { nullptr, reinterpret_cast(PodDefs::CapsuleMethod_handler), METH_VARARGS, nullptr }; for (int i = meta->propertyOffset(); i < meta->propertyCount(); ++i) { // Create a PropertyCapsule for each property to store the info needed // for the handler. auto metaProperty = meta->property(i); method.ml_name = metaProperty.name(); auto *capsule = PyCapsule_New(new PropertyCapsule{metaProperty.name(), i, i - meta->propertyOffset()}, "PropertyCapsule", [](PyObject *capsule) { delete static_cast( PyCapsule_GetPointer(capsule, "PropertyCapsule")); }); auto *capsulePropObject = make_capsule_property(&method, capsule); if (PyObject_SetAttrString(reinterpret_cast(type), metaProperty.name(), capsulePropObject) < 0) { return nullptr; } Py_DECREF(capsulePropObject); } // createConverter increases the ref count of type, but that will create // a circular reference. When we add the capsule with the converter's pointer // to the type's attributes. So we need to decrease the ref count on the type // after calling createConverter. auto *converter = Shiboken::Conversions::createConverter(type, cppToPython_POD_Tuple); Py_DECREF(type); if (set_cleanup_capsule_attr_for_pointer(type, "_converter_capsule", converter) < 0) return nullptr; Shiboken::Conversions::registerConverterName(converter, meta->className()); Shiboken::Conversions::registerConverterName(converter, type->tp_name); Shiboken::Conversions::addPythonToCppValueConversion(converter, pythonToCpp_Tuple_POD, is_Tuple_PythonToCpp_POD_Convertible); static PyObject *const module = String::createStaticString("PySide6.QtRemoteObjects"); AutoDecRef pyQualname(String::fromCString(qualname.constData())); PyObject_SetAttr(obType, PyMagicName::qualname(), pyQualname); PyObject_SetAttr(obType, PyMagicName::module(), module); PyObject_SetAttrString(obType, "__param_types__", pyParamTypes); return type; }