From 99f9352e7625e1118f683bfa96701593185bb2b8 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 1 Jan 2021 15:57:41 +0100 Subject: [PATCH] interrogate: improvements to __setstate__ handling: * Force single arg variant, easing argument parsing * Allow defining __setstate__ taking multiple args, leading to tuple unpack * Allow __setstate__ to be called on already initialized object (useful with __reduce__) --- dtool/src/interrogate/functionRemap.cxx | 3 ++ .../interfaceMakerPythonNative.cxx | 33 +++++++++++++------ 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/dtool/src/interrogate/functionRemap.cxx b/dtool/src/interrogate/functionRemap.cxx index fd44f9f5fa..e0dec969c0 100644 --- a/dtool/src/interrogate/functionRemap.cxx +++ b/dtool/src/interrogate/functionRemap.cxx @@ -920,6 +920,9 @@ setup_properties(const InterrogateFunction &ifunc, InterfaceMaker *interface_mak || fname == "__delattr__") { // Just to prevent these from getting keyword arguments. + } else if (fname == "__setstate__") { + _args_type = InterfaceMaker::AT_single_arg; + } else { if (_args_type == InterfaceMaker::AT_varargs) { // Every other method can take keyword arguments, if they take more diff --git a/dtool/src/interrogate/interfaceMakerPythonNative.cxx b/dtool/src/interrogate/interfaceMakerPythonNative.cxx index 91114c747c..18cbc3a22a 100644 --- a/dtool/src/interrogate/interfaceMakerPythonNative.cxx +++ b/dtool/src/interrogate/interfaceMakerPythonNative.cxx @@ -3616,18 +3616,23 @@ write_function_for_name(ostream &out, Object *obj, std::string ClassName = make_safe_name(obj->_itype.get_scoped_name()); std::string cClassName = obj->_itype.get_true_name(); // string class_name = remap->_cpptype->get_simple_name(); + CPPStructType *struct_type = obj->_itype._cpptype->as_struct_type(); // If this is a non-static __setstate__, we run the default constructor. - if (remap->_cppfunc->get_local_name() == "__setstate__") { - out << " if (DtoolInstance_VOID_PTR(self) != nullptr) {\n" - << " Dtool_Raise_TypeError(\"C++ object is already constructed.\");\n"; - error_return(out, 4, return_flags); - out << " }\n" - << " " << cClassName << " *local_this = new " << cClassName << ";\n" - << " DTool_PyInit_Finalize(self, local_this, " - << "((Dtool_PyInstDef *)self)->_My_Type, true, false);\n" - << " if (local_this == nullptr) {\n" - << " PyErr_NoMemory();\n"; + if (remap->_cppfunc->get_local_name() == "__setstate__" && + !struct_type->is_abstract()) { + out << " " << cClassName << " *local_this = nullptr;\n" + << " if (DtoolInstance_VOID_PTR(self) == nullptr) {\n" + << " local_this = new " << cClassName << ";\n" + << " DTool_PyInit_Finalize(self, local_this, &Dtool_" << ClassName + << ", true, false);\n" + << " if (local_this == nullptr) {\n" + << " PyErr_NoMemory();\n"; + error_return(out, 6, return_flags); + out << " }\n" + << " } else if (!Dtool_Call_ExtractThisPointer_NonConst(self, Dtool_" << ClassName << ", " + << "(void **)&local_this, \"" << classNameFromCppName(cClassName, false) + << "." << methodNameFromCppName(remap, cClassName, false) << "\")) {\n"; } else if (all_nonconst) { // All remaps are non-const. Also check that this object isn't const. @@ -3664,6 +3669,14 @@ write_function_for_name(ostream &out, Object *obj, args_type = AT_varargs; } + // If this is a __setstate__ taking multiple arguments, and we're given a + // tuple as argument, unpack it. + if (args_type == AT_single_arg && max_required_args > 1 && + remap->_cppfunc->get_local_name() == "__setstate__") { + out << " PyObject *args = arg;\n"; + args_type = AT_varargs; + } + if (args_type == AT_keyword_args || args_type == AT_varargs) { max_required_args = collapse_default_remaps(map_sets, max_required_args); }